import log from "electron-log/main";
import path from "path";
import fs from "fs/promises";
import axios from "axios";
import { create } from "xmlbuilder2";
import { parseStringPromise } from "xml2js";
import store from "../../store/store";
import client, { getTokenFromRenderer } from "../../graphql/graphql-client";
import { PaintScaleConfig } from "../../../util/types/paintScale";
import dayjs from "dayjs";
import {
  PPG_DATA_QUERY_TYPED,
  PpgDataQueryResult,
  PpgDataQueryVariables,
} from "../../graphql/queries";

export async function ppgInputHandler(config: PaintScaleConfig): Promise<void> {
  try {
    log.info(
      `Polling input directory for PPG config ${config.id}: ${config.path}`,
    );

    log.debug(
      `Archive dir: ${path.join(config.path!, "archive")}, Error dir: ${path.join(config.path!, "error")}`,
    );

    // Ensure archive and error directories exist
    const archiveDir = path.join(config.path!, "archive");
    const errorDir = path.join(config.path!, "error");
    try {
      await fs.mkdir(archiveDir, { recursive: true });
      await fs.mkdir(errorDir, { recursive: true });
      log.debug(
        `Archive and error directories ensured: ${archiveDir}, ${errorDir}`,
      );
    } catch (dirError) {
      log.error(`Failed to create directories for ${config.path}:`, dirError);
      throw dirError;
    }

    // Check for files
    const files = await fs.readdir(config.path!);
    log.debug(`Found ${files.length} files in ${config.path}:`, files);

    for (const file of files) {
      // Only process XML files
      if (!file.toLowerCase().endsWith(".xml")) {
        continue;
      }

      const filePath = path.join(config.path!, file);
      try {
        const stats = await fs.stat(filePath);
        if (!stats.isFile()) {
          continue;
        }
      } catch (statError) {
        log.warn(`Failed to stat file ${filePath}:`, statError);
        continue;
      }

      log.debug(`Processing input file: ${filePath}`);

      // Check file accessibility (e.g., not locked)
      try {
        await fs.access(filePath, fs.constants.R_OK);
      } catch (error) {
        log.warn(`File ${filePath} is inaccessible, skipping:`, error);
        continue;
      }

      // Validate XML structure
      let xmlContent: BlobPart;
      try {
        xmlContent = await fs.readFile(filePath, "utf8");
        await parseStringPromise(xmlContent);
        log.debug(`Successfully validated XML for ${filePath}`);
      } catch (error) {
        log.error(`Invalid XML in ${filePath}:`, error);
        const timestamp = dayjs().format("YYYYMMDD_HHmmss");
        const originalFilename = path.basename(file, path.extname(file));
        const errorPath = path.join(
          errorDir,
          `${originalFilename}-${timestamp}.xml`,
        );
        try {
          await fs.rename(filePath, errorPath);
          log.debug(`Moved invalid file to error: ${errorPath}`);
        } catch (moveError) {
          log.error(
            `Failed to move invalid file to error directory ${errorPath}:`,
            moveError,
          );
        }
        continue;
      }

      // Get authentication token
      let token: string | null;
      try {
        token = await getTokenFromRenderer();
        if (!token) {
          log.error(`No authentication token for file: ${filePath}`);
          continue;
        }
        log.debug(
          `Obtained authentication token for ${filePath}: ${token.slice(0, 10)}...`,
        );
      } catch (tokenError) {
        log.error(
          `Failed to obtain authentication token for ${filePath}:`,
          tokenError,
        );
        continue;
      }

      // Upload file to API
      const formData = new FormData();
      formData.append("file", new Blob([xmlContent]), path.basename(filePath));
      const shopId = (store.get("app.bodyshop") as any)?.shopname || "";
      formData.append("shopId", shopId);
      log.debug(`Shop ID: ${shopId}`);

      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}`);

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

        log.info(`Upload response for ${filePath}:`, {
          status: response.status,
          statusText: response.statusText,
          data: response.data,
        });

        if (response.status === 200) {
          log.info(`Successful upload of ${filePath}`);
          // Move file to archive
          const timestamp = dayjs().format("YYYYMMDD_HHmmss");
          const originalFilename = path.basename(file, path.extname(file));
          const archivePath = path.join(
            archiveDir,
            `${originalFilename}-${timestamp}.xml`,
          );
          try {
            await fs.access(archiveDir, fs.constants.W_OK); // Verify archiveDir is writable
            await fs.rename(filePath, archivePath);
            log.info(`Moved file to archive: ${archivePath}`);
          } catch (moveError) {
            log.error(
              `Failed to move file to archive directory ${archivePath}:`,
              moveError,
            );
          }
        } else {
          log.error(
            `Failed to upload ${filePath}: ${response.status} ${response.statusText}`,
            { responseData: response.data },
          );
        }
      } catch (error: any) {
        log.error(`Error uploading ${filePath}:`, {
          message: error.message,
          code: error.code,
          response: error.response
            ? {
                status: error.response.status,
                statusText: error.response.statusText,
                data: error.response.data,
              }
            : null,
        });
      }
    }
  } catch (error) {
    log.error(`Error polling input directory ${config.path}:`, error);
  }
}

// PPG Output Handler
export async function ppgOutputHandler(
  config: PaintScaleConfig,
): Promise<void> {
  try {
    log.info(`Generating PPG output for config ${config.id}: ${config.path}`);

    await fs.mkdir(config.path!, { recursive: true });

    const variables: PpgDataQueryVariables = {
      today: dayjs().toISOString(),
      todayplus5: dayjs().add(5, "day").toISOString(),
      shopid: (store.get("app.bodyshop") as any)?.id,
    };

    const response = await client.request<
      PpgDataQueryResult,
      PpgDataQueryVariables
    >(PPG_DATA_QUERY_TYPED, variables);
    const jobs = response.jobs ?? [];

    const header = {
      PPG: {
        Header: {
          Protocol: {
            Message: "PaintShopInterface",
            Name: "PPG",
            Version: "1.5.0",
          },
          Transaction: {
            TransactionID: "",
            TransactionDate: dayjs().format("YYYY-MM-DD:HH:mm"),
          },
          Product: {
            Name: import.meta.env.VITE_COMPANY === "IMEX",
            Version: "",
          },
        },
        DataInterface: {
          ROData: {
            ShopInfo: {
              ShopID: response.bodyshops_by_pk?.imexshopid || "",
              ShopName: response.bodyshops_by_pk?.shopname || "",
            },
            RepairOrders: {
              ROCount: jobs.length.toString(),
              RO: 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?.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?.sum?.mod_lb_hrs || 0,
                BodyLaborRate: job.rate_lab || 0,
                TotalCostOfRepairs: (
                  (job.job_totals?.totals?.subtotal?.amount || 0) / 100
                ).toFixed(2),
              })),
            },
          },
        },
      },
    };

    const xml = create({ version: "1.0" }, header).end({ prettyPrint: true });
    const outputPath = path.join(config.path!, `PPGPaint.xml`);
    await fs.writeFile(outputPath, xml);
    log.info(`Saved PPG output XML to ${outputPath}`);
  } catch (error) {
    log.error(`Error generating PPG output for config ${config.id}:`, error);
  }
}
