import { createHash, randomUUID } from 'node:crypto';
import {
  buildSupportedMappingsForFieldNames,
  bumpMinorTemplateVersion,
  classifyTemplateField,
  computeFieldDiff,
  createSqlClient,
  extractTemplateFields,
  getEffectiveMappedKeys,
  loadTemplateSourceByOrganizationName,
  summarizeIssues,
  type TemplateMappingSeed,
  type TemplateSourceVariant,
} from './pdf-audit-utils.ts';

type TemplateRow = {
  id: string;
  organizationId: string;
  organizationName: string;
  templateName: string;
  createdBy: string;
  updatedBy: string;
};

type TemplateVersionRow = {
  id: string;
  templateId: string;
  version: string;
  schemaJson: unknown;
  filePath: string | null;
  isActive: boolean;
  createdAt: string;
};

type ScriptOptions = {
  variant: TemplateSourceVariant;
  activate: boolean;
};

function computeSchemaHash(schemaJson: unknown): string {
  return createHash('sha256').update(JSON.stringify(schemaJson)).digest('hex');
}

function normalizeSchemaJson(schemaJson: unknown): unknown {
  if (typeof schemaJson === 'string') {
    return JSON.parse(schemaJson) as unknown;
  }

  return schemaJson;
}

function parseArgs(argv: string[]): ScriptOptions {
  let variant: TemplateSourceVariant = 'legacy';
  let activate = false;

  for (const arg of argv) {
    if (arg === '--activate') {
      activate = true;
      continue;
    }

    if (arg.startsWith('--template-variant=')) {
      const value = arg.slice('--template-variant='.length);

      if (value === 'legacy' || value === 'product-v1') {
        variant = value;
      }
    }
  }

  if (variant === 'legacy') {
    activate = true;
  }

  return { variant, activate };
}

function resolveVersionLabel(args: {
  variant: TemplateSourceVariant;
  versions: TemplateVersionRow[];
  activeVersion: TemplateVersionRow;
  matchingVersion: TemplateVersionRow | null;
}): string {
  if (args.matchingVersion) {
    return args.matchingVersion.version;
  }

  if (args.variant === 'product-v1') {
    return '2.0';
  }

  return bumpMinorTemplateVersion(args.versions.at(-1)?.version ?? args.activeVersion.version);
}

async function upsertMapping(
  sql: any,
  organizationId: string,
  templateVersionId: string,
  mapping: TemplateMappingSeed,
) {
  const existing = (
    await sql`
      select id
      from crm_document_template_mappings
      where template_version_id = ${templateVersionId}
        and placeholder_key = ${mapping.placeholderKey}
      limit 1
    `
  )[0] as { id: string } | undefined;

  const mappingId = existing?.id ?? randomUUID();

  if (existing) {
    await sql`
      update crm_document_template_mappings
      set
        organization_id = ${organizationId},
        source_path = ${mapping.sourcePath},
        data_type = ${mapping.dataType},
        sheet_name = ${null},
        default_value = ${mapping.defaultValue ?? null},
        format_mask = ${mapping.formatMask ?? null},
        sort_order = ${mapping.sortOrder},
        deleted_at = ${null},
        updated_at = now()
      where id = ${mappingId}
    `;
  } else {
    await sql`
      insert into crm_document_template_mappings (
        id,
        organization_id,
        template_version_id,
        placeholder_key,
        source_path,
        data_type,
        sheet_name,
        default_value,
        format_mask,
        sort_order
      ) values (
        ${mappingId},
        ${organizationId},
        ${templateVersionId},
        ${mapping.placeholderKey},
        ${mapping.sourcePath},
        ${mapping.dataType},
        ${null},
        ${mapping.defaultValue ?? null},
        ${mapping.formatMask ?? null},
        ${mapping.sortOrder}
      )
    `;
  }

  const desiredColumnNames = new Set((mapping.columns ?? []).map((column) => column.columnName));

  for (const column of mapping.columns ?? []) {
    const existingColumn = (
      await sql`
        select id
        from crm_document_template_table_columns
        where mapping_id = ${mappingId}
          and column_name = ${column.columnName}
          and deleted_at is null
        limit 1
      `
    )[0] as { id: string } | undefined;

    const columnId = existingColumn?.id ?? randomUUID();

    if (existingColumn) {
      await sql`
        update crm_document_template_table_columns
        set
          organization_id = ${organizationId},
          source_field = ${column.sourceField},
          column_letter = ${column.columnLetter ?? null},
          sort_order = ${column.sortOrder ?? 0},
          format_mask = ${column.formatMask ?? null},
          deleted_at = ${null},
          updated_at = now()
        where id = ${columnId}
      `;
    } else {
      await sql`
        insert into crm_document_template_table_columns (
          id,
          organization_id,
          mapping_id,
          column_name,
          source_field,
          column_letter,
          sort_order,
          format_mask
        ) values (
          ${columnId},
          ${organizationId},
          ${mappingId},
          ${column.columnName},
          ${column.sourceField},
          ${column.columnLetter ?? null},
          ${column.sortOrder ?? 0},
          ${column.formatMask ?? null}
        )
      `;
    }
  }

  const existingColumns = await sql`
    select id, column_name as "columnName"
    from crm_document_template_table_columns
    where mapping_id = ${mappingId}
      and deleted_at is null
  ` as Array<{ id: string; columnName: string }>;

  for (const column of existingColumns) {
    if (desiredColumnNames.has(column.columnName)) {
      continue;
    }

    await sql`
      update crm_document_template_table_columns
      set deleted_at = now(), updated_at = now()
      where id = ${column.id}
    `;
  }
}

async function main() {
  const options = parseArgs(process.argv.slice(2));
  const sql = createSqlClient();

  try {
    const templates = await sql`
      select
        t.id,
        t.organization_id as "organizationId",
        o.name as "organizationName",
        t.template_name as "templateName",
        t.created_by as "createdBy",
        t.updated_by as "updatedBy"
      from crm_document_templates t
      inner join organizations o on o.id = t.organization_id
      where t.document_type = 'quotation'
        and t.file_type = 'pdfme'
        and t.product_type = 'default'
        and t.deleted_at is null
      order by o.name asc, t.created_at asc
    ` as TemplateRow[];

    const results: Array<Record<string, unknown>> = [];

    for (const template of templates) {
      const templateSource = loadTemplateSourceByOrganizationName(
        template.organizationName,
        options.variant,
      );

      if (!templateSource) {
        results.push({
          organizationName: template.organizationName,
          templateName: template.templateName,
          status: 'WARNING',
          message: 'No source template file found for organization.',
        });
        continue;
      }

      const versions = await sql`
        select
          id,
          template_id as "templateId",
          version,
          schema_json as "schemaJson",
          file_path as "filePath",
          is_active as "isActive",
          created_at as "createdAt"
        from crm_document_template_versions
        where template_id = ${template.id}
          and deleted_at is null
        order by created_at asc
      ` as TemplateVersionRow[];

      const activeVersion = versions.find((version) => version.isActive) ?? versions.at(-1) ?? null;

      if (!activeVersion) {
        throw new Error(`Template ${template.templateName} has no version rows.`);
      }

      const targetSchema = templateSource.schemaJson;
      const targetSchemaHash = computeSchemaHash(targetSchema);
      const matchingVersion = versions.find(
        (version) => computeSchemaHash(normalizeSchemaJson(version.schemaJson)) === targetSchemaHash,
      ) ?? null;
      const previousFields = extractTemplateFields(normalizeSchemaJson(activeVersion.schemaJson));
      const targetFields = extractTemplateFields(targetSchema);
      const targetFieldNames = new Set(targetFields.map((field) => field.name).filter(Boolean));
      const targetTokens = new Set(targetFields.flatMap((field) => field.tokens));
      const supportedMappings = buildSupportedMappingsForFieldNames(
        targetFieldNames,
        targetTokens,
      );
      const mappedKeys = getEffectiveMappedKeys(targetFields, supportedMappings);
      const fieldDiff = computeFieldDiff(targetFields, previousFields);
      const createdNewVersion = !matchingVersion;
      const targetVersionLabel = resolveVersionLabel({
        variant: options.variant,
        versions,
        activeVersion,
        matchingVersion,
      });
      const targetVersionId = matchingVersion?.id ?? randomUUID();

      await sql.begin(async (tx) => {
        if (!matchingVersion) {
          await tx`
            insert into crm_document_template_versions (
              id,
              organization_id,
              template_id,
              version,
              file_path,
              schema_json,
              preview_image_url,
              is_active,
              deleted_at,
              created_by
            ) values (
              ${targetVersionId},
              ${template.organizationId},
              ${template.id},
              ${targetVersionLabel},
              ${templateSource.relativePath},
              ${JSON.stringify(targetSchema)},
              ${null},
              ${options.activate},
              ${null},
              ${template.updatedBy || template.createdBy}
            )
          `;
        } else {
          await tx`
            update crm_document_template_versions
            set
              file_path = ${templateSource.relativePath},
              is_active = ${options.activate ? true : matchingVersion.isActive},
              updated_at = now()
            where id = ${targetVersionId}
          `;
        }

        if (options.activate) {
          await tx`
            update crm_document_template_versions
            set
              is_active = case when id = ${targetVersionId} then true else false end,
              updated_at = now()
            where template_id = ${template.id}
              and deleted_at is null
          `;
        }

        for (const mapping of supportedMappings) {
          await upsertMapping(tx, template.organizationId, targetVersionId, mapping);
        }

        const desiredKeys = new Set(supportedMappings.map((mapping) => mapping.placeholderKey));
        const existingMappings = await tx`
          select id, placeholder_key as "placeholderKey"
          from crm_document_template_mappings
          where template_version_id = ${targetVersionId}
            and deleted_at is null
        ` as Array<{ id: string; placeholderKey: string }>;

        for (const mapping of existingMappings) {
          if (desiredKeys.has(mapping.placeholderKey)) {
            continue;
          }

          await tx`
            update crm_document_template_mappings
            set deleted_at = now(), updated_at = now()
            where id = ${mapping.id}
          `;
        }
      });

      const classificationCounts = new Map<string, number>();

      for (const field of targetFields) {
        const classification = classifyTemplateField(field, mappedKeys);
        classificationCounts.set(
          classification,
          (classificationCounts.get(classification) ?? 0) + 1,
        );
      }

      results.push({
        organizationName: template.organizationName,
        templateName: template.templateName,
        variant: options.variant,
        activated: options.activate,
        sourceFile: templateSource.relativePath,
        previousActiveVersion: activeVersion.version,
        targetVersion: targetVersionLabel,
        createdNewVersion,
        retainedInactiveVersions: versions.filter((version) => version.id !== targetVersionId).length,
        deletedFields: fieldDiff.deletedFields,
        newFields: fieldDiff.newFields,
        mappingKeys: supportedMappings.map((mapping) => mapping.placeholderKey),
        classificationCounts: Object.fromEntries(classificationCounts.entries()),
        status: summarizeIssues([
          supportedMappings.some((mapping) => mapping.placeholderKey === 'tel_label')
            ? 'PASS'
            : 'FAIL',
          options.variant === 'product-v1' &&
          supportedMappings.some((mapping) => mapping.placeholderKey === 'items_table')
            ? 'PASS'
            : 'PASS',
        ]),
      });
    }

    const overallStatus = summarizeIssues(
      results.map((result) => String(result.status) as 'PASS' | 'WARNING' | 'FAIL'),
    );

    console.log(
      JSON.stringify(
        {
          action: 'reseed-pdf-template-version',
          overallStatus,
          variant: options.variant,
          activate: options.activate,
          resultCount: results.length,
          results,
        },
        null,
        2,
      ),
    );
  } finally {
    await sql.end({ timeout: 5 });
  }
}

main().catch((error) => {
  console.error(`[reseed-pdf-template-version] ${error.message}`);
  process.exit(1);
});
