import { app, ipcMain } from "electron";
import log from "electron-log/main";
import { autoUpdater } from "electron-updater";
import path from "path";
import ipcTypes from "../../util/ipcTypes.json";
import ImportJob from "../decoder/decoder";
import store from "../store/store";
import { StartWatcher, StopWatcher } from "../watcher/watcher";
import {
  SettingEmsOutFilePathGet,
  SettingEmsOutFilePathSet,
  SettingsPaintScaleInputConfigsGet,
  SettingsPaintScaleInputConfigsSet,
  SettingsPaintScaleInputPathSet,
  SettingsPaintScaleOutputConfigsGet,
  SettingsPaintScaleOutputConfigsSet,
  SettingsPaintScaleOutputPathSet,
  SettingsPpcFilePathGet,
  SettingsPpcFilePathSet,
  SettingsWatchedFilePathsAdd,
  SettingsWatchedFilePathsGet,
  SettingsWatchedFilePathsRemove,
  SettingsWatcherPollingGet,
  SettingsWatcherPollingSet,
} from "./ipcMainHandler.settings";
import {
  ipcMainHandleAuthStateChanged,
  ipMainHandleResetPassword,
} from "./ipcMainHandler.user";
import cron from "node-cron";
import fs from "fs/promises";
import axios from "axios";
import { create } from "xmlbuilder2";
import client from "../graphql/graphql-client";
import { PaintScaleConfig, PaintScaleType } from "./paintScale";

// Add these interfaces at the top of your file
interface User {
  stsTokenManager?: {
    accessToken: string;
  };
}

interface BodyShop {
  shopname: string;
  id: string;
}

interface GraphQLResponse {
  bodyshops_by_pk?: {
    imexshopid: string;
    shopname: string;
  };
  jobs?: Array<{
    ro_number: string;
    ownr_ln: string;
    ownr_fn: string;
    plate_no: string;
    v_vin: string;
    v_model_yr: string;
    v_make_desc: string;
    v_model_desc: string;
    vehicle?: {
      v_paint_codes?: {
        paint_cd1: string;
      };
    };
    larhrs_aggregate?: {
      aggregate?: {
        sum?: {
          mod_lb_hrs: number;
        };
      };
    };
    ins_co_nm: string;
    est_ct_ln: string;
    est_ct_fn: string;
    job_totals?: {
      rates?: {
        mapa?: {
          total?: {
            amount: number;
          };
        };
      };
      totals?: {
        subtotal?: {
          amount: number;
        };
      };
    };
    rate_mapa: number;
    labhrs_aggregate?: {
      aggregate?: {
        sum?: {
          mod_lb_hrs: number;
        };
      };
    };
    rate_lab: number;
  }>;
}

const initializeCronTasks = async () => {
  try {
    // Fetch input and output configurations
    const inputConfigs = await SettingsPaintScaleInputConfigsGet();
    const outputConfigs = await SettingsPaintScaleOutputConfigsGet();

    // Start input cron tasks
    await handlePaintScaleInputCron(inputConfigs);
    log.info("Initialized input cron tasks on app startup");

    // Start output cron tasks
    await handlePaintScaleOutputCron(outputConfigs);
    log.info("Initialized output cron tasks on app startup");
  } catch (error) {
    log.error("Error initializing cron tasks on app startup:", error);
  }
};

// Log all IPC messages and their payloads
const logIpcMessages = (): void => {
  Object.keys(ipcTypes.toMain).forEach((key) => {
    const messageType = ipcTypes.toMain[key];
    const originalHandler = ipcMain.listeners(messageType)?.[0];
    if (originalHandler) {
      ipcMain.removeAllListeners(messageType);
    }
    ipcMain.on(messageType, (event, payload) => {
      log.info(
        `%c[IPC Main]%c${messageType}`,
        "color: red",
        "color: green",
        payload,
      );
      if (originalHandler) {
        originalHandler(event, payload);
      }
    });
  });
};

// Input handler map
const inputTypeHandlers: Record<
  PaintScaleType,
  (config: PaintScaleConfig) => Promise<void>
> = {
  [PaintScaleType.PPG]: async (config: PaintScaleConfig) => {
    try {
      log.info(
        `Polling input directory for PPG config ${config.id}: ${config.path}`,
      );

      // Ensure archive directory exists
      const archiveDir = path.join(config.path!, "archive");
      await fs.mkdir(archiveDir, { recursive: true });

      // Check for files
      const files = await fs.readdir(config.path!);
      for (const file of files) {
        // Only process XML files
        if (!file.toLowerCase().endsWith(".xml")) {
          continue;
        }

        const filePath = path.join(config.path!, file);
        const stats = await fs.stat(filePath);
        if (stats.isFile()) {
          log.debug(`Processing input file: ${filePath}`);

          // Get authentication token
          const token = (store.get("user") as User)?.stsTokenManager
            ?.accessToken;
          if (!token) {
            log.error(`No authentication token for file: ${filePath}`);
            continue;
          }

          // Upload file to API
          const formData = new FormData();

          formData.append(
            "file",
            new Blob([await fs.readFile(filePath)]),
            path.basename(filePath),
          );
          formData.append(
            "shopId",
            (store.get("app.bodyshop") as BodyShop)?.shopname || "",
          );

          const baseURL = store.get("app.isTest")
            ? import.meta.env.VITE_API_TEST_URL
            : import.meta.env.VITE_API_URL;

          const finalUrl = `${baseURL}/mixdata/upload`;

          log.debug(`Uploading file to ${finalUrl}`);

          const response = await axios.post(finalUrl, formData, {
            headers: {
              Authorization: `Bearer ${token}`,
              "Content-Type": "multipart/form-data",
            },
          });

          if (response.status === 200) {
            log.info(`Successful upload of ${filePath}`);
            // Move file to archive
            const archivePath = path.join(archiveDir, path.basename(filePath));
            await fs.rename(filePath, archivePath);
            log.debug(`Moved file to archive: ${archivePath}`);
          } else {
            log.error(`Failed to upload ${filePath}: ${response.statusText}`);
          }
        }
      }
    } catch (error) {
      log.error(`Error polling input directory ${config.path}:`, error);
    }
  },
  // Add other input type handlers as needed
};

// Output handler map
const outputTypeHandlers: Record<
  PaintScaleType,
  (config: PaintScaleConfig) => Promise<void>
> = {
  [PaintScaleType.PPG]: async (config: PaintScaleConfig) => {
    try {
      log.info(`Generating PPG output for config ${config.id}: ${config.path}`);

      // Ensure output directory exists
      await fs.mkdir(config.path!, { recursive: true });

      // GraphQL query for jobs
      const query = `
      query PpgData($today: date!, $todayplus5: date!, $shopid: uuid!) {
        bodyshops_by_pk(id: $shopid) {
          imexshopid
          shopname
        }
        jobs(where: {
          _and: [
            { shop_id: { _eq: $shopid } },
            { _or: [
              { status: { _eq: "Active" } },
              { scheduled_date: { _gte: $today, _lte: $todayplus5 } }
            ] }
          ]
        }) {
          ro_number
          ownr_ln
          ownr_fn
          plate_no
          v_vin
          v_model_yr
          v_make_desc
          v_model_desc
          vehicle {
            v_paint_codes {
              paint_cd1
            }
          }
          larhrs_aggregate: estimate_lines_aggregate(where: { line_type: { _eq: "Paint" } }) {
            aggregate {
              sum {
                mod_lb_hrs
              }
            }
          }
          ins_co_nm
          est_ct_ln
          est_ct_fn
          job_totals {
            rates {
              mapa {
                total {
                  amount
                }
              }
            }
            totals {
              subtotal {
                amount
              }
            }
          }
          rate_mapa
          labhrs_aggregate: estimate_lines_aggregate(where: { line_type: { _eq: "Body" } }) {
            aggregate {
              sum {
                mod_lb_hrs
              }
            }
          }
          rate_lab
        }
      }
    `;

      const variables = {
        today: new Date().toISOString().split("T")[0],
        todayplus5: new Date(Date.now() + 5 * 24 * 60 * 60 * 1000)
          .toISOString()
          .split("T")[0],
        shopid: (store.get("app.bodyshop") as BodyShop)?.id,
      };

      const response = (await client.request(
        query,
        variables,
      )) as GraphQLResponse;

      // Build JSON structure
      const jsonData = {
        PPG: {
          Header: {
            Protocol: {
              Message: "PaintShopInterface",
              Name: "PPG",
              Version: "1.5.0",
            },
            Transaction: {
              TransactionID: "",
              TransactionDate: new Date()
                .toISOString()
                .replace("T", " ")
                .substring(0, 19),
            },
            Product: {
              Name: "ImEX Online",
              Version: "",
            },
          },
          DataInterface: {
            ROData: {
              ShopInfo: {
                ShopID: response.bodyshops_by_pk?.imexshopid || "",
                ShopName: response.bodyshops_by_pk?.shopname || "",
              },
              RepairOrders: {
                ROCount: (response.jobs?.length || 0).toString(),
                RO:
                  response.jobs?.map((job) => ({
                    RONumber: job.ro_number || "",
                    ROStatus: "Open",
                    Customer: `${job.ownr_ln || ""}, ${job.ownr_fn || ""}`,
                    ROPainterNotes: "",
                    LicensePlateNum: job.plate_no || "",
                    VIN: job.v_vin || "",
                    ModelYear: job.v_model_yr || "",
                    MakeDesc: job.v_make_desc || "",
                    ModelName: job.v_model_desc || "",
                    OEMColorCode: job.vehicle?.v_paint_codes?.paint_cd1 || "",
                    RefinishLaborHours:
                      job.larhrs_aggregate?.aggregate?.sum?.mod_lb_hrs || 0,
                    InsuranceCompanyName: job.ins_co_nm || "",
                    EstimatorName: `${job.est_ct_ln || ""}, ${job.est_ct_fn || ""}`,
                    PaintMaterialsRevenue: (
                      (job.job_totals?.rates?.mapa?.total?.amount || 0) / 100
                    ).toFixed(2),
                    PaintMaterialsRate: job.rate_mapa || 0,
                    BodyHours:
                      job.labhrs_aggregate?.aggregate?.sum?.mod_lb_hrs || 0,
                    BodyLaborRate: job.rate_lab || 0,
                    TotalCostOfRepairs: (
                      (job.job_totals?.totals?.subtotal?.amount || 0) / 100
                    ).toFixed(2),
                  })) || [],
              },
            },
          },
        },
      };

      // Convert JSON to XML
      const doc = create({ version: "1.0" }, jsonData);
      const xmlString = doc.end({ prettyPrint: true });

      // Save XML to file
      const outputPath = path.join(config.path!, `PPGPaint.xml`);
      await fs.writeFile(outputPath, xmlString);
      log.info(`Saved PPG output XML to ${outputPath}`);
    } catch (error) {
      log.error(`Error generating PPG output for config ${config.id}:`, error);
    }
  }, // Add other output type handlers as needed
};

// Default handler for unsupported types
const defaultHandler = async (config: PaintScaleConfig) => {
  log.debug(
    `No handler defined for type ${config.type} in config ${config.id}`,
  );
};

// Input cron job management
let inputCronTasks: { [id: string]: cron.ScheduledTask } = {};

const handlePaintScaleInputCron = async (configs: PaintScaleConfig[]) => {
  Object.values(inputCronTasks).forEach((task) => task.stop());
  inputCronTasks = {};

  const validConfigs = configs.filter(
    (config) => config.path && config.path.trim() !== "",
  );

  validConfigs.forEach((config) => {
    const cronExpression = `*/${config.pollingInterval} * * * *`;
    inputCronTasks[config.id] = cron.schedule(cronExpression, async () => {
      const handler = inputTypeHandlers[config.type] || defaultHandler;
      await handler(config);
    });
    log.info(
      `Started input cron task for config ${config.id} (type: ${config.type}) with interval ${config.pollingInterval}m`,
    );
  });
};

// Output cron job management
let outputCronTasks: { [id: string]: cron.ScheduledTask } = {};

const handlePaintScaleOutputCron = async (configs: PaintScaleConfig[]) => {
  Object.values(outputCronTasks).forEach((task) => task.stop());
  outputCronTasks = {};

  const validConfigs = configs.filter(
    (config) => config.path && config.path.trim() !== "",
  );

  validConfigs.forEach((config) => {
    const cronExpression = `*/${config.pollingInterval} * * * *`;
    outputCronTasks[config.id] = cron.schedule(cronExpression, async () => {
      const handler = outputTypeHandlers[config.type] || defaultHandler;
      await handler(config);
    });
    log.info(
      `Started output cron task for config ${config.id} (type: ${config.type}) with interval ${config.pollingInterval}m`,
    );
  });
};

// Existing IPC handlers...

ipcMain.on(ipcTypes.toMain.test, () =>
  console.log("** Verify that ipcMain is loaded and working."),
);

// Auth handler
ipcMain.on(ipcTypes.toMain.authStateChanged, ipcMainHandleAuthStateChanged);
ipcMain.on(ipcTypes.toMain.user.resetPassword, ipMainHandleResetPassword);

// Add debug handlers if in development
if (import.meta.env.DEV) {
  log.debug("[IPC Debug Functions] Adding Debug Handlers");

  ipcMain.on(ipcTypes.toMain.debug.decodeEstimate, async (): Promise<void> => {
    const relativeEmsFilepath = `_reference/ems/MPI_1/3698420.ENV`;
    const rootDir = app.getAppPath();
    const absoluteFilepath = path.join(rootDir, relativeEmsFilepath);

    log.debug("[IPC Debug Function] Decode test Estimate", absoluteFilepath);
    await ImportJob(absoluteFilepath);

    const job2 = `/Users/pfic/Downloads/12285264/2285264.ENV`;
    const job3 = `/Users/pfic/Downloads/14033376/4033376.ENV`;
    await ImportJob(job2);
    await ImportJob(job3);
  });
}

// Settings Handlers
ipcMain.handle(
  ipcTypes.toMain.settings.filepaths.get,
  SettingsWatchedFilePathsGet,
);
ipcMain.handle(
  ipcTypes.toMain.settings.filepaths.add,
  SettingsWatchedFilePathsAdd,
);
ipcMain.handle(
  ipcTypes.toMain.settings.filepaths.remove,
  SettingsWatchedFilePathsRemove,
);
ipcMain.handle(
  ipcTypes.toMain.settings.watcher.getpolling,
  SettingsWatcherPollingGet,
);
ipcMain.handle(
  ipcTypes.toMain.settings.watcher.setpolling,
  SettingsWatcherPollingSet,
);

ipcMain.handle(ipcTypes.toMain.settings.getPpcFilePath, SettingsPpcFilePathGet);
ipcMain.handle(ipcTypes.toMain.settings.setPpcFilePath, SettingsPpcFilePathSet);
ipcMain.handle(
  ipcTypes.toMain.settings.getEmsOutFilePath,
  SettingEmsOutFilePathGet,
);
ipcMain.handle(
  ipcTypes.toMain.settings.setEmsOutFilePath,
  SettingEmsOutFilePathSet,
);

// Paint Scale Input Settings Handlers
ipcMain.handle(
  ipcTypes.toMain.settings.paintScale.getInputConfigs,
  SettingsPaintScaleInputConfigsGet,
);
ipcMain.handle(
  ipcTypes.toMain.settings.paintScale.setInputConfigs,
  SettingsPaintScaleInputConfigsSet,
);
ipcMain.handle(
  ipcTypes.toMain.settings.paintScale.setInputPath,
  SettingsPaintScaleInputPathSet,
);

// Paint Scale Output Settings Handlers
ipcMain.handle(
  ipcTypes.toMain.settings.paintScale.getOutputConfigs,
  SettingsPaintScaleOutputConfigsGet,
);
ipcMain.handle(
  ipcTypes.toMain.settings.paintScale.setOutputConfigs,
  SettingsPaintScaleOutputConfigsSet,
);
ipcMain.handle(
  ipcTypes.toMain.settings.paintScale.setOutputPath,
  SettingsPaintScaleOutputPathSet,
);

// IPC handlers for updating paint scale cron
ipcMain.on(
  ipcTypes.toMain.settings.paintScale.updateInputCron,
  (_event, configs: PaintScaleConfig[]) => {
    handlePaintScaleInputCron(configs).catch((error) => {
      log.error(`Error handling paint scale input cron for configs: ${error}`);
    });
  },
);

ipcMain.on(
  ipcTypes.toMain.settings.paintScale.updateOutputCron,
  (_event, configs: PaintScaleConfig[]) => {
    handlePaintScaleOutputCron(configs).catch((error) => {
      log.error(`Error handling paint scale output cron for configs: ${error}`);
    });
  },
);

ipcMain.handle(ipcTypes.toMain.user.getActiveShop, () => {
  return store.get("app.bodyshop.shopname");
});

// Watcher Handlers
ipcMain.on(ipcTypes.toMain.watcher.start, () => {
  StartWatcher().catch((error) => {
    log.error("Error starting watcher:", error);
  });
});

ipcMain.on(ipcTypes.toMain.watcher.stop, () => {
  StopWatcher().catch((error) => {
    log.error("Error stopping watcher:", error);
  });
});

ipcMain.on(ipcTypes.toMain.updates.download, () => {
  log.info("Download update requested from renderer.");
  autoUpdater.downloadUpdate().catch((error) => {
    log.error("Error downloading update:", error);
  });
});

export { initializeCronTasks };

logIpcMessages();
