import type { TenantSummary } from "../../../lib/adminApi";

export type TenantCSVRow = {
  rowNumber: number;
  tenantId: string;
  name: string;
  type: string;
  parentTenantId: string;
  parentTenantSlug: string;
  slug: string;
  memo: string;
  emailDomain: string;
  visibility: string;
  orgUnitType: string;
  worksmobileSync: string;
};

export type TenantCSVParseOptions = {
  rootParentSlug?: string;
};

type TenantCSVSourceKey = keyof TenantCSVRow | "mailingList" | "parentOrg";

export type TenantImportCandidate = {
  tenantId: string;
  name: string;
  slug: string;
  score: number;
  reason: "exact_name" | "exact_slug" | "similar_name";
};

export type TenantImportPreviewRow = {
  row: TenantCSVRow;
  candidates: TenantImportCandidate[];
  defaultTenantId: string;
  defaultCreateSlug: string;
  conflicts: TenantImportConflict[];
};

export type TenantImportParentOptionGroupType =
  | "COMPANY_GROUP"
  | "COMPANY"
  | "ORGANIZATION";

export type TenantImportParentOptionGroup = {
  type: TenantImportParentOptionGroupType;
  tenants: TenantSummary[];
};

export type TenantImportConflict =
  | "external_tenant_id"
  | "slug_exists"
  | "parent_tenant_id_unresolved";

export type TenantImportResolution =
  | {
      mode: "existing";
      tenantId: string;
      parentTenantId?: string;
      parentTenantSlug?: string;
    }
  | {
      mode: "create";
      tenantId?: string;
      slug?: string;
      parentTenantId?: string;
      parentTenantSlug?: string;
    }
  | {
      mode: "skip";
    };

const importHeaders = [
  "tenant_id",
  "name",
  "type",
  "parent_tenant_id",
  "parent_tenant_slug",
  "slug",
  "memo",
  "email_domain",
  "visibility",
  "org_unit_type",
  "worksmobile_sync",
];

const headerAliases: Record<string, TenantCSVSourceKey> = {
  id: "tenantId",
  tenantid: "tenantId",
  tenant_id: "tenantId",
  name: "name",
  조직명: "name",
  type: "type",
  parentid: "parentTenantId",
  parent_id: "parentTenantId",
  parenttenantid: "parentTenantId",
  parent_tenant_id: "parentTenantId",
  parenttenantslug: "parentTenantSlug",
  parent_tenant_slug: "parentTenantSlug",
  상위_조직: "parentOrg",
  slug: "slug",
  memo: "memo",
  description: "memo",
  설명: "memo",
  메일링_리스트: "mailingList",
  "email-domain": "emailDomain",
  emaildomain: "emailDomain",
  email_domain: "emailDomain",
  domain: "emailDomain",
  domains: "emailDomain",
  visibility: "visibility",
  public_setting: "visibility",
  publicsetting: "visibility",
  orgunittype: "orgUnitType",
  org_unit_type: "orgUnitType",
  "org-unit-type": "orgUnitType",
  organizationtype: "orgUnitType",
  organization_type: "orgUnitType",
  orgtype: "orgUnitType",
  org_type: "orgUnitType",
  worksmobile: "worksmobileSync",
  worksmobilesync: "worksmobileSync",
  worksmobile_sync: "worksmobileSync",
  works_sync: "worksmobileSync",
  works: "worksmobileSync",
};

export function parseTenantCSV(
  text: string,
  options: TenantCSVParseOptions = {},
): TenantCSVRow[] {
  const records = parseCSV(text.replace(/^\uFEFF/, ""));
  if (records.length === 0) return [];

  const header = new Map<TenantCSVSourceKey, number>();
  records[0].forEach((column, index) => {
    const normalized = normalizeHeader(column);
    const key = headerAliases[normalized];
    if (key) header.set(key, index);
  });

  const isOrgChartCSV = header.has("mailingList") || header.has("parentOrg");
  const sourceRows = records.slice(1).flatMap((record, index) => {
    if (record.every((value) => value.trim() === "")) return [];
    const value = (key: TenantCSVSourceKey) => {
      const columnIndex = header.get(key);
      if (columnIndex === undefined) return "";
      return (record[columnIndex] ?? "").trim();
    };

    return {
      raw: record,
      rowNumber: index + 2,
      name: value("name"),
      slug: value("slug") || slugFromMailingList(value("mailingList")),
      mailingList: value("mailingList"),
      parentOrg: value("parentOrg"),
      value,
    };
  });
  const slugByName = new Map(
    sourceRows
      .filter((row) => row.name && row.slug)
      .map((row) => [row.name, row.slug] as const),
  );

  return sourceRows.map(({ rowNumber, name, slug, parentOrg, value }) => {
    const parentTenantSlug =
      value("parentTenantSlug") ||
      slugFromParentOrg(parentOrg, slugByName) ||
      (isOrgChartCSV ? options.rootParentSlug || "" : "");

    return {
      rowNumber,
      tenantId: value("tenantId"),
      name,
      type: value("type") || (isOrgChartCSV ? "ORGANIZATION" : ""),
      parentTenantId: value("parentTenantId"),
      parentTenantSlug,
      slug,
      memo: value("memo"),
      emailDomain: value("emailDomain"),
      visibility: value("visibility"),
      orgUnitType: value("orgUnitType"),
      worksmobileSync: normalizeWorksmobileSync(value("worksmobileSync")),
    };
  });
}

export function inferTenantImportRootParentSlug(
  fileName: string,
  tenants: TenantSummary[] = [],
) {
  const baseName = fileName.trim().split(/[\\/]/).pop()?.toLowerCase() ?? "";
  const [prefix = ""] = baseName.split("_");
  if (!prefix) return "";

  const existingTenant = tenants.find(
    (tenant) => tenant.slug.toLowerCase() === prefix,
  );
  return existingTenant ? prefix : "";
}

export function buildTenantImportParentOptionGroups(
  tenants: TenantSummary[],
): TenantImportParentOptionGroup[] {
  const orderedTypes: TenantImportParentOptionGroupType[] = [
    "COMPANY_GROUP",
    "COMPANY",
    "ORGANIZATION",
  ];

  return orderedTypes
    .map((type) => ({
      type,
      tenants: tenants.filter((tenant) => tenant.type?.toUpperCase() === type),
    }))
    .filter((group) => group.tenants.length > 0);
}

export function buildTenantImportPreview(
  rows: TenantCSVRow[],
  tenants: TenantSummary[],
): TenantImportPreviewRow[] {
  return rows
    .map((row) => {
      const candidates = findTenantCandidates(row, tenants);
      const conflicts = findTenantImportConflicts(row, tenants);
      return {
        row,
        candidates,
        conflicts,
        defaultTenantId:
          candidates[0] && candidates[0].score >= 0.95
            ? candidates[0].tenantId
            : "",
        defaultCreateSlug: suggestUniqueTenantSlug(
          row.slug || row.name,
          tenants,
        ),
      };
    })
    .sort((a, b) => {
      const aScore = a.candidates[0]?.score ?? 0;
      const bScore = b.candidates[0]?.score ?? 0;
      if (bScore !== aScore) return bScore - aScore;
      return a.row.rowNumber - b.row.rowNumber;
    });
}

export function serializeTenantImportCSV(
  previewRows: TenantImportPreviewRow[],
  selectedTenantIds: Record<number, string | TenantImportResolution>,
) {
  const lines = [importHeaders];
  const sortedRows = [...previewRows].sort(
    (a, b) => a.row.rowNumber - b.row.rowNumber,
  );
  const targetTenantIds = buildTargetTenantIds(sortedRows, selectedTenantIds);

  for (const preview of sortedRows) {
    const resolution = selectedTenantIds[preview.row.rowNumber] ?? "";
    if (typeof resolution === "object" && resolution.mode === "skip") {
      continue;
    }

    const selectedTenantId =
      typeof resolution === "string"
        ? resolution
        : resolution.mode === "existing"
          ? resolution.tenantId
          : "";
    const slug =
      typeof resolution === "object" && resolution.mode === "create"
        ? resolution.slug || preview.defaultCreateSlug
        : preview.row.slug;
    const hasParentTenantIdOverride =
      typeof resolution === "object" &&
      Object.hasOwn(resolution, "parentTenantId");
    const hasParentTenantSlugOverride =
      typeof resolution === "object" &&
      Object.hasOwn(resolution, "parentTenantSlug");
    const sourceParentTenantSlug = hasParentTenantSlugOverride
      ? resolution.parentTenantSlug || ""
      : preview.row.parentTenantSlug;
    const parentTenantId =
      typeof resolution === "object"
        ? hasParentTenantIdOverride
          ? resolution.parentTenantId || ""
          : remapParentTenantId(
              preview.row.parentTenantId,
              sourceParentTenantSlug,
              targetTenantIds,
            )
        : preview.row.parentTenantId;
    const parentTenantSlug = remapParentTenantSlug(
      sourceParentTenantSlug,
      targetTenantIds,
    );
    const tenantId =
      targetTenantIds.byRowNumber.get(preview.row.rowNumber) ??
      selectedTenantId ??
      preview.row.tenantId;

    lines.push([
      tenantId,
      preview.row.name,
      preview.row.type,
      parentTenantId,
      parentTenantSlug,
      slug,
      preview.row.memo,
      preview.row.emailDomain,
      preview.row.visibility,
      preview.row.orgUnitType,
      preview.row.worksmobileSync || "yes",
    ]);
  }
  return `${lines.map(formatCSVRecord).join("\n")}\n`;
}

function buildTargetTenantIds(
  previewRows: TenantImportPreviewRow[],
  selectedTenantIds: Record<number, string | TenantImportResolution>,
) {
  const byRowNumber = new Map<number, string>();
  const bySourceId = new Map<string, string>();
  const bySourceSlug = new Map<string, string>();
  const bySourceSlugToTargetSlug = new Map<string, string>();

  for (const preview of previewRows) {
    const resolution = selectedTenantIds[preview.row.rowNumber] ?? "";
    if (typeof resolution === "object" && resolution.mode === "skip") {
      continue;
    }

    const sourceTenantId = isUUIDLikeTenantId(preview.row.tenantId)
      ? preview.row.tenantId
      : "";
    const targetTenantId =
      typeof resolution === "string"
        ? resolution || sourceTenantId
        : resolution.mode === "existing"
          ? resolution.tenantId
          : resolution.tenantId || sourceTenantId || createTenantImportId();
    const targetSlug =
      typeof resolution === "object" && resolution.mode === "create"
        ? resolution.slug || preview.defaultCreateSlug
        : preview.row.slug;

    if (targetTenantId) {
      byRowNumber.set(preview.row.rowNumber, targetTenantId);
    }
    if (preview.row.tenantId) {
      bySourceId.set(preview.row.tenantId, targetTenantId);
    }
    if (preview.row.slug) {
      bySourceSlug.set(preview.row.slug.toLowerCase(), targetTenantId);
      bySourceSlugToTargetSlug.set(preview.row.slug.toLowerCase(), targetSlug);
    }
    if (targetSlug) {
      bySourceSlug.set(targetSlug.toLowerCase(), targetTenantId);
      bySourceSlugToTargetSlug.set(targetSlug.toLowerCase(), targetSlug);
    }
  }

  return { byRowNumber, bySourceId, bySourceSlug, bySourceSlugToTargetSlug };
}

function remapParentTenantId(
  parentTenantId: string,
  parentTenantSlug: string,
  targetTenantIds: {
    byRowNumber: Map<number, string>;
    bySourceId: Map<string, string>;
    bySourceSlug: Map<string, string>;
    bySourceSlugToTargetSlug: Map<string, string>;
  },
) {
  if (parentTenantId) {
    return targetTenantIds.bySourceId.get(parentTenantId) ?? parentTenantId;
  }
  if (parentTenantSlug) {
    return (
      targetTenantIds.bySourceSlug.get(parentTenantSlug.toLowerCase()) ?? ""
    );
  }
  return "";
}

function remapParentTenantSlug(
  parentTenantSlug: string,
  targetTenantIds: {
    bySourceSlugToTargetSlug: Map<string, string>;
  },
) {
  if (!parentTenantSlug) return "";
  return (
    targetTenantIds.bySourceSlugToTargetSlug.get(
      parentTenantSlug.toLowerCase(),
    ) ?? parentTenantSlug
  );
}

function createTenantImportId() {
  if (globalThis.crypto?.randomUUID) {
    return globalThis.crypto.randomUUID();
  }
  return `00000000-0000-4000-8000-${Math.random()
    .toString(16)
    .slice(2, 14)
    .padEnd(12, "0")}`;
}

function isUUIDLikeTenantId(value: string) {
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
    value,
  );
}

function findTenantImportConflicts(
  row: TenantCSVRow,
  tenants: TenantSummary[],
): TenantImportConflict[] {
  const conflicts: TenantImportConflict[] = [];
  const matchingId = row.tenantId
    ? tenants.find((tenant) => tenant.id === row.tenantId)
    : undefined;
  const matchingSlug = row.slug
    ? tenants.find(
        (tenant) => normalizeToken(tenant.slug) === normalizeToken(row.slug),
      )
    : undefined;

  if (row.tenantId && !matchingId) {
    conflicts.push("external_tenant_id");
  }
  if (matchingSlug && matchingSlug.id !== row.tenantId) {
    conflicts.push("slug_exists");
  }
  if (
    row.parentTenantId &&
    !tenants.some((tenant) => tenant.id === row.parentTenantId)
  ) {
    conflicts.push("parent_tenant_id_unresolved");
  }

  return conflicts;
}

function findTenantCandidates(
  row: TenantCSVRow,
  tenants: TenantSummary[],
): TenantImportCandidate[] {
  return tenants
    .map((tenant) => {
      const nameScore = similarity(row.name, tenant.name);
      const slugScore =
        normalizeToken(row.slug) &&
        normalizeToken(row.slug) === normalizeToken(tenant.slug)
          ? 0.98
          : 0;
      const exactName =
        normalizeToken(row.name) === normalizeToken(tenant.name);
      const score = exactName ? 1 : Math.max(slugScore, nameScore);
      const reason: TenantImportCandidate["reason"] = exactName
        ? "exact_name"
        : slugScore >= 0.98
          ? "exact_slug"
          : "similar_name";
      return {
        tenantId: tenant.id,
        name: tenant.name,
        slug: tenant.slug,
        score,
        reason,
      };
    })
    .filter((candidate) => candidate.score >= 0.45)
    .sort((a, b) => b.score - a.score)
    .slice(0, 3);
}

function parseCSV(text: string): string[][] {
  const rows: string[][] = [];
  let current = "";
  let row: string[] = [];
  let quoted = false;

  for (let i = 0; i < text.length; i += 1) {
    const char = text[i];
    const next = text[i + 1];

    if (char === '"' && quoted && next === '"') {
      current += '"';
      i += 1;
      continue;
    }
    if (char === '"') {
      quoted = !quoted;
      continue;
    }
    if (char === "," && !quoted) {
      row.push(current);
      current = "";
      continue;
    }
    if ((char === "\n" || char === "\r") && !quoted) {
      if (char === "\r" && next === "\n") i += 1;
      row.push(current);
      rows.push(row);
      row = [];
      current = "";
      continue;
    }
    current += char;
  }

  if (current !== "" || row.length > 0) {
    row.push(current);
    rows.push(row);
  }

  return rows;
}

function formatCSVRecord(record: string[]) {
  return record
    .map((value) => {
      if (!/[",\r\n]/.test(value)) return value;
      return `"${value.replaceAll('"', '""')}"`;
    })
    .join(",");
}

function normalizeHeader(value: string) {
  return value.trim().toLowerCase().replaceAll(" ", "_");
}

function normalizeWorksmobileSync(value: string) {
  const normalized = value.trim().toLowerCase();
  if (
    [
      "no",
      "n",
      "false",
      "0",
      "off",
      "none",
      "excluded",
      "exclude",
      "not_sync",
      "not-synced",
      "미연동",
      "연동안함",
      "제외",
    ].includes(normalized)
  ) {
    return "no";
  }
  return "yes";
}

function slugFromMailingList(value: string) {
  if (!value) return "";
  return normalizeTenantSlug(value.split("@")[0] ?? value);
}

function slugFromParentOrg(value: string, slugByName: Map<string, string>) {
  const trimmed = value.trim();
  if (!trimmed) return "";
  const match = trimmed.match(/\(([^)]+)\)/);
  if (match?.[1]) {
    return slugFromMailingList(match[1]);
  }
  return slugByName.get(trimmed) ?? normalizeTenantSlug(trimmed);
}

function normalizeTenantSlug(value: string) {
  let slug = value
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-");
  slug = slug.replace(/^-+|-+$/g, "");
  if (slug.length > 25) {
    slug = slug.slice(0, 25).replace(/-+$/g, "");
  }
  return slug;
}

function normalizeToken(value: string) {
  return value
    .trim()
    .toLowerCase()
    .replace(/[\s_-]+/g, "")
    .replace(/[^\p{L}\p{N}]/gu, "");
}

function suggestUniqueTenantSlug(value: string, tenants: TenantSummary[]) {
  const base = slugify(value) || "tenant";
  const used = new Set(tenants.map((tenant) => tenant.slug.toLowerCase()));
  if (!used.has(base)) {
    return base;
  }

  let index = 2;
  while (used.has(`${base}-${index}`)) {
    index += 1;
  }
  return `${base}-${index}`;
}

function slugify(value: string) {
  // 한글 조직명을 영어로 유추하거나 정규화하는 맵 (자주 쓰이는 단어)
  const commonMappings: Record<string, string> = {
    총괄기획실: "gpd",
    기술개발센터: "tdc",
    경영기획: "planning",
    영업: "sales",
    인프라: "infra",
    건설: "construction",
    운영: "ops",
    환경: "env",
    사업: "biz",
    본부: "hq",
    부: "dept",
    팀: "team",
    지원: "support",
  };

  const result = value.trim();

  // 1. 전체 매칭 확인
  if (commonMappings[result]) {
    return commonMappings[result];
  }

  // 2. 부분 단어 치환 및 정규화
  return result
    .toLowerCase()
    .replace(/[^a-z0-9가-힣ㄱ-ㅎㅏ-ㅣ]+/g, "-") // 특수문자 제거
    .split("-")
    .map((part) => commonMappings[part] || part) // 부분 단어 변환
    .join("-")
    .replace(/^-+|-+$/g, "");
}

function similarity(left: string, right: string) {
  const a = normalizeToken(left);
  const b = normalizeToken(right);
  if (!a || !b) return 0;
  if (a === b) return 1;
  if (a.includes(b) || b.includes(a)) {
    return Math.min(a.length, b.length) / Math.max(a.length, b.length);
  }

  const distance = levenshtein(a, b);
  return 1 - distance / Math.max(a.length, b.length);
}

function levenshtein(left: string, right: string) {
  const previous = Array.from({ length: right.length + 1 }, (_, i) => i);
  const current = Array.from({ length: right.length + 1 }, () => 0);

  for (let i = 1; i <= left.length; i += 1) {
    current[0] = i;
    for (let j = 1; j <= right.length; j += 1) {
      const cost = left[i - 1] === right[j - 1] ? 0 : 1;
      current[j] = Math.min(
        current[j - 1] + 1,
        previous[j] + 1,
        previous[j - 1] + cost,
      );
    }
    previous.splice(0, previous.length, ...current);
  }

  return previous[right.length];
}
