import * as XLSX from "xlsx";
import { parseUnknownDate } from "@/lib/date";
import type { ImportParseResult, ParsedEventRow } from "@/lib/types";
import {
  buildHeaderMap,
  canonicalHeader,
  findHeaderRow,
  normalizeHeaderToken,
  type CanonicalHeader,
  REQUIRED_FIELDS,
} from "@/lib/import/headers";
import { normalizeImportedBrandValue } from "@/lib/import/brand-normalization";
import { parsedEventRowSchema } from "@/lib/import/schema";

type SentimentCounts = {
  thumbs_up: number;
  thumbs_down: number;
};

type SentimentHeaderMap = {
  sent_email_name?: number;
  activity_type?: number;
};

const SENTIMENT_ACTIVITY_HEADER_ALIASES = new Set([
  "multichannelactivitytype",
  "activitytype",
  "multichanneltype",
  "approvedemailaction",
  "otapprovedemailaction",
]);

function toText(value: unknown) {
  if (value === null || value === undefined) return "";
  return String(value).trim();
}

function toMetricInt(value: unknown) {
  if (typeof value === "number") return Math.max(0, Math.round(value));

  const raw = toText(value).toLowerCase();
  if (!raw) return 0;
  if (raw === "yes" || raw === "true") return 1;
  if (raw === "no" || raw === "false") return 0;

  const numeric = Number(raw.replace(/,/g, ""));
  if (Number.isFinite(numeric)) return Math.max(0, Math.round(numeric));
  return 0;
}

function getCell(row: unknown[], map: Partial<Record<CanonicalHeader, number>>, field: CanonicalHeader) {
  const index = map[field];
  if (index === undefined) return undefined;
  return row[index];
}

function rowHasAnyData(row: unknown[], map: Partial<Record<CanonicalHeader, number>>) {
  return Object.values(map).some((index) => {
    if (index === undefined) return false;
    const value = row[index];
    return toText(value) !== "";
  });
}

function hasRequiredValues(row: ParsedEventRow) {
  return REQUIRED_FIELDS.every((field) => {
    if (field === "primary_country_code") return Boolean(row.primary_country_code);
    if (field === "approved_document_name") return Boolean(row.approved_document_name);
    if (field === "sent_email_name") return Boolean(row.sent_email_name);
    return true;
  });
}

function normalizeDocumentName(value: string) {
  return value.trim().replace(/\s+/g, " ");
}

function normalizeSentEmailName(value: string) {
  return value.trim();
}

function classifySentimentDirection(value: string): "up" | "down" | null {
  const raw = value.trim().toLowerCase();
  if (!raw) return null;

  if (raw.includes("_no") || /\b(no|thumbs?down|down|negative|dislike)\b/.test(raw)) {
    return "down";
  }

  if (raw.includes("_yes") || /\b(yes|thumbs?up|up|positive|like)\b/.test(raw)) {
    return "up";
  }

  return null;
}

function buildSentimentHeaderMap(row: unknown[]): SentimentHeaderMap {
  const mapping: SentimentHeaderMap = {};

  row.forEach((cell, index) => {
    if (mapping.sent_email_name === undefined && canonicalHeader(cell) === "sent_email_name") {
      mapping.sent_email_name = index;
      return;
    }

    if (mapping.activity_type === undefined) {
      const token = normalizeHeaderToken(cell);
      if (SENTIMENT_ACTIVITY_HEADER_ALIASES.has(token)) {
        mapping.activity_type = index;
      }
    }
  });

  return mapping;
}

function findSentimentHeaderRow(rows: unknown[][], maxScan = 60) {
  const scanLimit = Math.min(rows.length, maxScan);

  for (let i = 0; i < scanLimit; i += 1) {
    const map = buildSentimentHeaderMap(rows[i] ?? []);
    if (map.sent_email_name !== undefined && map.activity_type !== undefined) {
      return { rowIndex: i, map };
    }
  }

  return null;
}

const expectedFields: CanonicalHeader[] = [
  "primary_country_code",
  "approved_document_name",
  "sent_email_name",
  "brand",
  "last_open",
  "opened",
  "total_opens",
  "last_click",
  "clicked",
  "total_clicks",
  "sent_date",
];

function parseRowsFromSheet(
  sheetName: string,
  rawRows: unknown[][],
): { rows: ParsedEventRow[]; warnings: string[]; documentBrands: Record<string, string> } | null {
  const header = findHeaderRow(rawRows);
  if (!header) return null;

  const map = buildHeaderMap(rawRows[header.rowIndex] ?? []);
  const warnings: string[] = [];
  const rows: ParsedEventRow[] = [];
  const documentBrands: Record<string, string> = {};
  const unmappedBrandValues = new Set<string>();

  const missingColumns = expectedFields.filter((field) => map[field] === undefined);
  if (missingColumns.length > 0) {
    warnings.push(`[${sheetName}] Missing optional columns: ${missingColumns.join(", ")}`);
  }

  for (let i = header.rowIndex + 1; i < rawRows.length; i += 1) {
    const source = rawRows[i] ?? [];
    if (!rowHasAnyData(source, map)) continue;

    const brandText = toText(getCell(source, map, "brand"));
    const normalizedBrand = brandText ? normalizeImportedBrandValue(brandText) : null;
    if (brandText && !normalizedBrand) {
      unmappedBrandValues.add(brandText);
    }

    const candidate: ParsedEventRow = {
      primary_country_code: toText(getCell(source, map, "primary_country_code")).toUpperCase(),
      approved_document_name: toText(getCell(source, map, "approved_document_name")) || "Untitled Document",
      sent_email_name: toText(getCell(source, map, "sent_email_name")),
      brand: normalizedBrand || undefined,
      last_open: parseUnknownDate(getCell(source, map, "last_open")),
      opened: toMetricInt(getCell(source, map, "opened")),
      total_opens: toMetricInt(getCell(source, map, "total_opens")),
      last_click: parseUnknownDate(getCell(source, map, "last_click")),
      clicked: toMetricInt(getCell(source, map, "clicked")),
      total_clicks: toMetricInt(getCell(source, map, "total_clicks")),
      sent_date: parseUnknownDate(getCell(source, map, "sent_date")),
      source_row_number: i + 1,
      source_sheet_name: sheetName,
    };

    if (!hasRequiredValues(candidate)) {
      warnings.push(`[${sheetName}] Row ${i + 1} skipped: missing required values.`);
      continue;
    }

    const parsed = parsedEventRowSchema.safeParse(candidate);
    if (!parsed.success) {
      warnings.push(
        `[${sheetName}] Row ${i + 1} skipped: ${parsed.error.issues[0]?.message ?? "Invalid row"}`,
      );
      continue;
    }

    if (parsed.data.brand) {
      const key = normalizeDocumentName(parsed.data.approved_document_name);
      if (key) {
        documentBrands[key] = parsed.data.brand;
      }
    }

    rows.push(parsed.data);
  }

  if (unmappedBrandValues.size > 0) {
    warnings.push(
      `[${sheetName}] Unmapped brand/product values ignored: ${[...unmappedBrandValues]
        .sort((a, b) => a.localeCompare(b))
        .slice(0, 20)
        .join(", ")}${unmappedBrandValues.size > 20 ? "..." : ""}`,
    );
  }

  return { rows, warnings, documentBrands };
}

function parseSummaryBrandTablesFromSheet(rawRows: unknown[][]) {
  const documentBrands: Record<string, string> = {};
  const unmappedBrandValues = new Set<string>();

  for (let i = 0; i < rawRows.length; i += 1) {
    const row = rawRows[i] ?? [];
    const map = buildHeaderMap(row);

    const docIndex = map.approved_document_name;
    const brandIndex = map.brand;

    if (docIndex === undefined || brandIndex === undefined) continue;

    for (let j = i + 1; j < rawRows.length; j += 1) {
      const source = rawRows[j] ?? [];
      const doc = normalizeDocumentName(toText(source[docIndex]));
      const brand = toText(source[brandIndex]);

      if (!doc && !brand) continue;

      if (!doc) {
        continue;
      }

      const docLooksLikeHeader = canonicalHeader(doc) !== null;
      if (docLooksLikeHeader) {
        continue;
      }

      if (!brand) continue;

      const normalizedBrand = normalizeImportedBrandValue(brand);
      if (!normalizedBrand) {
        unmappedBrandValues.add(brand);
        continue;
      }

      documentBrands[doc] = normalizedBrand;
    }
  }

  return {
    documentBrands,
    warnings:
      unmappedBrandValues.size > 0
        ? [
            `Unmapped brand/product values ignored: ${[...unmappedBrandValues]
              .sort((a, b) => a.localeCompare(b))
              .slice(0, 20)
              .join(", ")}${unmappedBrandValues.size > 20 ? "..." : ""}`,
          ]
        : [],
  };
}

function parseSentimentRowsFromSheet(
  sheetName: string,
  rawRows: unknown[][],
): {
  sentimentByEmail: Record<string, SentimentCounts>;
  sentimentRows: number;
  warnings: string[];
} {
  const sentimentByEmail: Record<string, SentimentCounts> = {};
  const warnings: string[] = [];

  const header = findSentimentHeaderRow(rawRows);
  if (!header) {
    return { sentimentByEmail, sentimentRows: 0, warnings };
  }

  const sentEmailIndex = header.map.sent_email_name;
  const activityIndex = header.map.activity_type;

  if (sentEmailIndex === undefined || activityIndex === undefined) {
    return { sentimentByEmail, sentimentRows: 0, warnings };
  }

  let sentimentRows = 0;

  for (let i = header.rowIndex + 1; i < rawRows.length; i += 1) {
    const source = rawRows[i] ?? [];
    const sentEmailName = normalizeSentEmailName(toText(source[sentEmailIndex]));
    const activityType = toText(source[activityIndex]);

    if (!sentEmailName && !activityType) continue;
    if (!sentEmailName) continue;

    const direction = classifySentimentDirection(activityType);
    if (!direction) {
      if (activityType) {
        warnings.push(`[${sheetName}] Row ${i + 1} sentiment skipped: unsupported activity type \"${activityType}\".`);
      }
      continue;
    }

    const current = sentimentByEmail[sentEmailName] ?? { thumbs_up: 0, thumbs_down: 0 };
    if (direction === "up") {
      current.thumbs_up += 1;
    } else {
      current.thumbs_down += 1;
    }

    sentimentByEmail[sentEmailName] = current;
    sentimentRows += 1;
  }

  return { sentimentByEmail, sentimentRows, warnings };
}

function mergeBrandMaps(...maps: Array<Record<string, string>>) {
  const merged: Record<string, string> = {};

  for (const map of maps) {
    for (const [documentName, brand] of Object.entries(map)) {
      const normalizedDocument = normalizeDocumentName(documentName);
      const normalizedBrand = toText(brand);
      if (!normalizedDocument || !normalizedBrand) continue;
      merged[normalizedDocument] = normalizedBrand;
    }
  }

  return merged;
}

function mergeSentimentMaps(...maps: Array<Record<string, SentimentCounts>>) {
  const merged: Record<string, SentimentCounts> = {};

  for (const map of maps) {
    for (const [sentEmailNameRaw, counts] of Object.entries(map)) {
      const sentEmailName = normalizeSentEmailName(sentEmailNameRaw);
      if (!sentEmailName) continue;

      const current = merged[sentEmailName] ?? { thumbs_up: 0, thumbs_down: 0 };
      current.thumbs_up += Math.max(0, Math.round(Number(counts.thumbs_up ?? 0)));
      current.thumbs_down += Math.max(0, Math.round(Number(counts.thumbs_down ?? 0)));
      merged[sentEmailName] = current;
    }
  }

  return merged;
}

export function parseExcelImport(fileBuffer: ArrayBuffer): ImportParseResult {
  const workbook = XLSX.read(fileBuffer, { type: "array" });
  if (workbook.SheetNames.length === 0) {
    return {
      rows: [],
      previewRows: [],
      documentBrands: {},
      sentimentByEmail: {},
      sentimentRows: 0,
      warnings: [],
      errors: ["Workbook has no sheets."],
      detectedCountries: [],
    };
  }

  const allRows: ParsedEventRow[] = [];
  const warnings: string[] = [];
  let documentBrands: Record<string, string> = {};
  let sentimentByEmail: Record<string, SentimentCounts> = {};
  let sentimentRows = 0;

  for (const sheetName of workbook.SheetNames) {
    const sheet = workbook.Sheets[sheetName];
    if (!sheet) continue;

    const rawRows = XLSX.utils.sheet_to_json<unknown[]>(sheet, {
      header: 1,
      raw: true,
      blankrows: false,
      defval: "",
    });

    const parsedSheet = parseRowsFromSheet(sheetName, rawRows);
    if (parsedSheet) {
      allRows.push(...parsedSheet.rows);
      warnings.push(...parsedSheet.warnings);
      documentBrands = mergeBrandMaps(documentBrands, parsedSheet.documentBrands);
    }

    const summaryBrands = parseSummaryBrandTablesFromSheet(rawRows);
    documentBrands = mergeBrandMaps(documentBrands, summaryBrands.documentBrands);
    warnings.push(...summaryBrands.warnings.map((warning) => `[${sheetName}] ${warning}`));

    const parsedSentiment = parseSentimentRowsFromSheet(sheetName, rawRows);
    sentimentByEmail = mergeSentimentMaps(sentimentByEmail, parsedSentiment.sentimentByEmail);
    sentimentRows += parsedSentiment.sentimentRows;
    warnings.push(...parsedSentiment.warnings);
  }

  const errors: string[] = [];
  if (allRows.length === 0 && sentimentRows === 0) {
    errors.push(
      "No valid raw event sheets were found. Required headers are: Primary Country [**], Approved Document Name, Sent Email Name.",
    );
  }

  const detectedCountries = [...new Set(allRows.map((row) => row.primary_country_code).filter(Boolean))].sort();

  return {
    rows: allRows,
    previewRows: allRows.slice(0, 25),
    documentBrands,
    sentimentByEmail,
    sentimentRows,
    warnings,
    errors,
    detectedCountries,
  };
}
