import { and, eq, inArray, isNull } from 'drizzle-orm';
import { crmCustomerContacts, crmCustomers, crmQuotations, organizations } from '@/db/schema';
import { db } from '@/lib/db';
import { AuthError } from '@/lib/auth/session';
import {
  listApprovalRequests,
  getApprovalRequest
} from '@/features/foundation/approval/server/service';
import {
  mapDocumentDataToTemplateInput,
  resolveTemplateForDocument,
  resolveTemplateMappings
} from '@/features/foundation/document-template/server/service';
import { getActiveOptionsByCategory } from '@/features/foundation/master-options/service';
import {
  getQuotationDetail,
  listQuotationCustomers,
  listQuotationItems,
  listQuotationTopics
} from '@/features/crm/quotations/server/service';
import type {
  ApprovedQuotationSnapshot,
  QuotationDocumentApprovalStep,
  QuotationDocumentData
} from '../types';
import type { PdfTopic } from './pdf-topic.type';
import { formatPdfCurrency, formatPdfDate, formatTopicItems } from './pdfme-transforms';
import { buildPdfTopicTemplate } from './pdf-topic-engine';
import { resolveQuotationSignatures } from './signature-resolver';
import { resolveQuotationTopicTypeMapping } from './topic-mapping';
import type { Template } from '@pdfme/common';

const DOCUMENT_OPTION_CATEGORIES = {
  branch: 'crm_branch',
  status: 'crm_quotation_status',
  quotationType: 'crm_quotation_type',
  currency: 'crm_currency',
  discountType: 'crm_discount_type',
  unit: 'crm_unit',
  customerRole: 'crm_project_party_role',
  productType: 'crm_product_type',
  topicType: 'crm_quotation_topic_type'
} as const;

function toRevisionLabel(revision: number) {
  return `R${String(revision).padStart(2, '0')}`;
}

async function assertQuotationDocumentAccess(quotationId: string, organizationId: string) {
  const [quotation] = await db
    .select()
    .from(crmQuotations)
    .where(
      and(
        eq(crmQuotations.id, quotationId),
        eq(crmQuotations.organizationId, organizationId),
        isNull(crmQuotations.deletedAt)
      )
    )
    .limit(1);

  if (!quotation) {
    throw new AuthError('Quotation not found', 404);
  }

  return quotation;
}

function findOptionLabel(
  options: Array<{ id: string; code: string; label: string }>,
  id: string | null | undefined
) {
  return id ? (options.find((option) => option.id === id)?.label ?? null) : null;
}

function findOptionCode(
  options: Array<{ id: string; code: string; label: string }>,
  id: string | null | undefined
) {
  return id ? (options.find((option) => option.id === id)?.code ?? null) : null;
}

function extractSinglePlaceholder(value: string) {
  const matches = [...value.matchAll(/\{([^}]+)\}/g)].map((match) => match[1]);
  const uniqueMatches = [...new Set(matches)];

  return uniqueMatches.length === 1 ? uniqueMatches[0] : null;
}

function normalizeTopicKey(value: string | null | undefined) {
  return (
    value
      ?.trim()
      .toLowerCase()
      .replaceAll(/[\s_-]+/g, '') ?? ''
  );
}

function matchesTopicAlias(actualCode: string | null | undefined, expectedCode: string) {
  const actual = normalizeTopicKey(actualCode);
  const expected = normalizeTopicKey(expectedCode);

  if (!actual || !expected) {
    return false;
  }

  if (actual === expected) {
    return true;
  }

  const aliasGroups = [
    ['scope', 'scopeofwork', 'dockscopeofwork', 'solarcellscopeofwork'],
    [
      'exclusion',
      'exclusionfromscopeofsupply',
      'exclusionofsupplyinstallation',
      'solarcellexclusionofsupplyinstallation'
    ],
    [
      'payment',
      'paymentconditions',
      'termsofpaymentuponprogressofeachitem',
      'solarcellpaymentconditions'
    ],
    ['delivery', 'deliverydate', 'solarcelldelivery'],
    ['warranty', 'solarcellwarranty']
  ];

  return aliasGroups.some((group) => group.includes(actual) && group.includes(expected));
}

function sortBySortOrder<T extends { sortOrder?: number | null }>(items: T[]) {
  return [...items].sort((left, right) => {
    const delta =
      (left.sortOrder ?? Number.MAX_SAFE_INTEGER) - (right.sortOrder ?? Number.MAX_SAFE_INTEGER);

    if (delta !== 0) {
      return delta;
    }

    return 0;
  });
}

function normalizePdfmeTemplateInput(schemaJson: unknown, templateInput: Record<string, unknown>) {
  const pages =
    schemaJson && typeof schemaJson === 'object' && 'schemas' in schemaJson
      ? (schemaJson as { schemas?: unknown }).schemas
      : null;

  if (!Array.isArray(pages)) {
    return templateInput;
  }

  const normalized = { ...templateInput };

  for (const page of pages) {
    if (!Array.isArray(page)) {
      continue;
    }

    for (const field of page) {
      if (!field || typeof field !== 'object') {
        continue;
      }

      const tableField = field as { type?: unknown; content?: unknown };

      if (tableField.type !== 'table' || typeof tableField.content !== 'string') {
        continue;
      }

      const placeholderKey = extractSinglePlaceholder(tableField.content);

      if (!placeholderKey) {
        continue;
      }

      const value = normalized[placeholderKey];

      if (typeof value !== 'string') {
        continue;
      }

      const lines = value
        .split('\n')
        .map((line) => line.trim())
        .filter(Boolean);

      if (!lines.length) {
        continue;
      }

      normalized[placeholderKey] = JSON.stringify(lines.map((line) => [line]));
    }
  }

  return normalized;
}

export async function buildQuotationDocumentData(
  quotationId: string,
  organizationId: string
): Promise<QuotationDocumentData> {
  const quotation = await assertQuotationDocumentAccess(quotationId, organizationId);
  const [
    company,
    quotationDetail,
    items,
    quotationCustomers,
    topics,
    branchOptions,
    statusOptions,
    quotationTypeOptions,
    currencyOptions,
    discountTypeOptions,
    unitOptions,
    customerRoleOptions,
    productTypeOptions,
    topicTypeOptions,
    approvalRequests
  ] = await Promise.all([
    db
      .select()
      .from(organizations)
      .where(eq(organizations.id, organizationId))
      .then((rows) => rows[0] ?? null),
    getQuotationDetail(quotationId, organizationId),
    listQuotationItems(quotationId, organizationId),
    listQuotationCustomers(quotationId, organizationId),
    listQuotationTopics(quotationId, organizationId),
    getActiveOptionsByCategory(DOCUMENT_OPTION_CATEGORIES.branch, { organizationId }),
    getActiveOptionsByCategory(DOCUMENT_OPTION_CATEGORIES.status, { organizationId }),
    getActiveOptionsByCategory(DOCUMENT_OPTION_CATEGORIES.quotationType, { organizationId }),
    getActiveOptionsByCategory(DOCUMENT_OPTION_CATEGORIES.currency, { organizationId }),
    getActiveOptionsByCategory(DOCUMENT_OPTION_CATEGORIES.discountType, { organizationId }),
    getActiveOptionsByCategory(DOCUMENT_OPTION_CATEGORIES.unit, { organizationId }),
    getActiveOptionsByCategory(DOCUMENT_OPTION_CATEGORIES.customerRole, { organizationId }),
    getActiveOptionsByCategory(DOCUMENT_OPTION_CATEGORIES.productType, { organizationId }),
    getActiveOptionsByCategory(DOCUMENT_OPTION_CATEGORIES.topicType, { organizationId }),
    listApprovalRequests(organizationId, {
      entityType: 'quotation',
      entityId: quotationId,
      limit: 20
    })
  ]);

  if (!company) {
    throw new AuthError('Organization not found', 404);
  }

  const [customer, contact, relatedCustomers, signatures] = await Promise.all([
    db
      .select()
      .from(crmCustomers)
      .where(
        and(
          eq(crmCustomers.id, quotation.customerId),
          eq(crmCustomers.organizationId, organizationId),
          isNull(crmCustomers.deletedAt)
        )
      )
      .then((rows) => rows[0] ?? null),
    quotation.contactId
      ? db
          .select()
          .from(crmCustomerContacts)
          .where(
            and(
              eq(crmCustomerContacts.id, quotation.contactId),
              eq(crmCustomerContacts.organizationId, organizationId),
              isNull(crmCustomerContacts.deletedAt)
            )
          )
          .then((rows) => rows[0] ?? null)
      : Promise.resolve(null),
    quotationCustomers.length
      ? db
          .select({ id: crmCustomers.id, code: crmCustomers.code, name: crmCustomers.name })
          .from(crmCustomers)
          .where(
            and(
              eq(crmCustomers.organizationId, organizationId),
              inArray(
                crmCustomers.id,
                quotationCustomers.map((item) => item.customerId)
              ),
              isNull(crmCustomers.deletedAt)
            )
          )
      : Promise.resolve([]),
    resolveQuotationSignatures(quotationId, organizationId)
  ]);

  if (!customer) {
    throw new AuthError('Customer not found', 404);
  }

  const approvalRequest =
    approvalRequests.items.find((item) => item.status === 'approved') ??
    approvalRequests.items[0] ??
    null;
  const approvalDetail = approvalRequest
    ? await getApprovalRequest(approvalRequest.id, organizationId)
    : null;
  const branch = branchOptions.find((option) => option.id === quotation.branchId) ?? null;
  const relatedCustomerMap = new Map(relatedCustomers.map((item) => [item.id, item]));
  const statusCode = findOptionCode(statusOptions, quotation.status);
  const statusLabel = findOptionLabel(statusOptions, quotation.status);
  const topicCodeById = new Map(topicTypeOptions.map((option) => [option.id, option.code]));
  const primaryProductType =
    items.find((item) => findOptionCode(productTypeOptions, item.productType))?.productType ?? null;
  const productTypeCode =
    findOptionCode(productTypeOptions, primaryProductType) ??
    findOptionCode(quotationTypeOptions, quotationDetail.quotationType) ??
    findOptionLabel(quotationTypeOptions, quotationDetail.quotationType) ??
    null;
  const topicTypeMapping = resolveQuotationTopicTypeMapping(productTypeCode);
  const resolvedTopics = sortBySortOrder(topics).map((item) => {
    const topicLabel =
      findOptionLabel(topicTypeOptions, item.topicType) ??
      item.title?.trim() ??
      topicCodeById.get(item.topicType) ??
      'Topic';

    return {
      id: item.id,
      title: item.title,
      topicType: topicLabel,
      topicCode: topicCodeById.get(item.topicType) ?? item.title,
      sortOrder: item.sortOrder,
      items: sortBySortOrder(item.items).map((child) => ({
        id: child.id,
        content: child.content,
        sortOrder: child.sortOrder
      }))
    };
  });
  const pdfTopics: PdfTopic[] = resolvedTopics.map((item) => ({
    id: item.id,
    topicType: item.topicType,
    sortOrder: item.sortOrder,
    items: item.items.map((child) => ({
      id: child.id,
      content: child.content?.trim() || '-',
      sortOrder: child.sortOrder
    }))
  }));
  const scopeTopics = resolvedTopics.filter((item) =>
    matchesTopicAlias(item.topicCode, topicTypeMapping.scope)
  );
  const exclusionTopics = resolvedTopics.filter((item) =>
    matchesTopicAlias(item.topicCode, topicTypeMapping.exclusion)
  );
  const paymentTopics = resolvedTopics.filter((item) =>
    matchesTopicAlias(item.topicCode, topicTypeMapping.payment)
  );
  const warrantyTopics = resolvedTopics.filter((item) =>
    matchesTopicAlias(item.topicCode, topicTypeMapping.warranty)
  );
  const deliveryTopics = resolvedTopics.filter((item) =>
    matchesTopicAlias(item.topicCode, topicTypeMapping.delivery)
  );
  const otherTopics = resolvedTopics.filter((item) => {
    return ![
      topicTypeMapping.scope,
      topicTypeMapping.exclusion,
      topicTypeMapping.payment,
      topicTypeMapping.warranty,
      topicTypeMapping.delivery
    ].some((expectedCode) => matchesTopicAlias(item.topicCode, expectedCode));
  });
  const exclusionTopic = exclusionTopics[0];
  const paymentTopic = paymentTopics[0];
  const warrantyTopic = warrantyTopics[0];
  const deliveryTopic = deliveryTopics[0];
  const approvalApprovers: QuotationDocumentApprovalStep[] =
    approvalDetail?.timeline
      .filter((item) => ['approve', 'reject', 'return'].includes(item.action))
      .map((item) => {
        const step = approvalDetail.steps.find(
          (candidate) => candidate.stepNumber === item.stepNumber
        );
        return {
          stepNumber: item.stepNumber,
          roleCode: step?.roleCode ?? '',
          roleName: step?.roleName ?? `Step ${item.stepNumber}`,
          action: item.action,
          actorName: item.actorName,
          actedAt: item.actedAt,
          remark: item.remark
        };
      }) ?? [];

  return {
    company: {
      id: company.id,
      name: company.name,
      slug: company.slug,
      imageUrl: company.imageUrl,
      plan: company.plan
    },
    branch: branch
      ? {
          id: branch.id,
          code: branch.code,
          label: branch.label,
          value: branch.value
        }
      : null,
    customer: {
      id: customer.id,
      code: customer.code,
      name: customer.name,
      address: customer.address,
      phone: customer.phone,
      email: customer.email,
      taxId: customer.taxId
    },
    contact: contact
      ? {
          id: contact.id,
          name: contact.name,
          email: contact.email,
          mobile: contact.mobile,
          position: contact.position
        }
      : null,
    quotation: {
      id: quotationDetail.id,
      code: quotationDetail.code,
      quotationDate: quotationDetail.quotationDate,
      validUntil: quotationDetail.validUntil,
      projectName: quotationDetail.projectName,
      projectLocation: quotationDetail.projectLocation,
      attention: quotationDetail.attention,
      reference: quotationDetail.reference,
      revision: quotationDetail.revision,
      revisionLabel: toRevisionLabel(quotationDetail.revision),
      statusCode,
      statusLabel,
      quotationTypeCode: findOptionCode(quotationTypeOptions, quotationDetail.quotationType),
      quotationTypeLabel: findOptionLabel(quotationTypeOptions, quotationDetail.quotationType),
      currency: findOptionCode(currencyOptions, quotationDetail.currency),
      currencyCode: findOptionCode(currencyOptions, quotationDetail.currency),
      currencyLabel: findOptionLabel(currencyOptions, quotationDetail.currency),
      exchangeRate: quotationDetail.exchangeRate,
      subtotal: quotationDetail.subtotal,
      discount: quotationDetail.discount,
      discountTypeCode: findOptionCode(discountTypeOptions, quotationDetail.discountType),
      discountTypeLabel: findOptionLabel(discountTypeOptions, quotationDetail.discountType),
      taxRate: quotationDetail.taxRate,
      taxAmount: quotationDetail.taxAmount,
      totalAmount: quotationDetail.totalAmount,
      notes: quotationDetail.notes
    },
    items: items.map((item) => ({
      id: item.id,
      itemNumber: item.itemNumber,
      productTypeCode: findOptionCode(productTypeOptions, item.productType),
      productTypeLabel: findOptionLabel(productTypeOptions, item.productType),
      description: item.description,
      quantity: item.quantity,
      unitCode: findOptionCode(unitOptions, item.unit),
      unitLabel: findOptionLabel(unitOptions, item.unit),
      unitPrice: item.unitPrice,
      discount: item.discount,
      discountTypeCode: findOptionCode(discountTypeOptions, item.discountType),
      taxRate: item.taxRate,
      totalPrice: item.totalPrice,
      notes: item.notes
    })),
    quotationCustomers: quotationCustomers.map((item) => ({
      id: item.id,
      customerId: item.customerId,
      customerName: relatedCustomerMap.get(item.customerId)?.name ?? item.customerName,
      customerCode: relatedCustomerMap.get(item.customerId)?.code ?? item.customerCode,
      roleCode: item.roleCode,
      roleLabel: item.roleLabel ?? findOptionLabel(customerRoleOptions, item.role),
      isPrimary: item.isPrimary
    })),
    topics: {
      scope: scopeTopics.map((item) => ({
        title: item.title,
        items: item.items.map((child) => child.content ?? '-')
      })),
      exclusion: exclusionTopics.map((item) => ({
        title: item.title,
        items: item.items.map((child) => child.content ?? '-')
      })),
      payment: paymentTopics.map((item) => ({
        title: item.title,
        items: item.items.map((child) => child.content ?? '-')
      })),
      warranty: warrantyTopics.map((item) => ({
        title: item.title,
        items: item.items.map((child) => child.content ?? '-')
      })),
      delivery: deliveryTopics.map((item) => ({
        title: item.title,
        items: item.items.map((child) => child.content ?? '-')
      })),
      other: otherTopics.map((item) => ({
        id: item.id,
        title: item.title,
        sortOrder: item.sortOrder,
        items: item.items.map((child) => child.content ?? '-')
      })),
      all: pdfTopics
    },
    pdfme: {
      quotation_date: formatPdfDate(quotationDetail.quotationDate, 'en'),
      quotation_price: formatPdfCurrency(
        quotationDetail.totalAmount,
        findOptionCode(currencyOptions, quotationDetail.currency) ?? 'THB'
      ),
      exclusion_data: formatTopicItems(exclusionTopic),
      payment_data: formatTopicItems(paymentTopic),
      warranty_data: formatTopicItems(warrantyTopic),
      delivery_data: formatTopicItems(deliveryTopic),
      topics_data: formatTopicItems(scopeTopics[0] ?? exclusionTopic ?? paymentTopic),
      topic_inputs: {}
    },
    approval: {
      requestId: approvalDetail?.request.id ?? null,
      workflowName: approvalDetail?.workflow.name ?? null,
      status: approvalDetail?.request.status ?? null,
      approvedAt:
        approvalDetail?.request.status === 'approved' ? approvalDetail.request.completedAt : null,
      currentStep: approvalDetail?.request.currentStep ?? null,
      currentStepRoleName: approvalDetail?.currentStep?.roleName ?? null,
      approvers: approvalApprovers
    },
    signatures,
    watermarkStatus: statusCode && statusCode !== 'approved' ? (statusLabel ?? statusCode) : null
  };
}

export async function getQuotationDocumentPreviewData(quotationId: string, organizationId: string) {
  const documentData = await buildQuotationDocumentData(quotationId, organizationId);
  const template = await resolveTemplateForDocument(organizationId, {
    documentType: 'quotation',
    productType: 'default',
    fileType: 'pdfme'
  });
  const mappings = await resolveTemplateMappings(template.version.id, organizationId);

  if (!mappings.length) {
    throw new AuthError(
      'Document template mappings are not configured for this template version',
      409
    );
  }

  const templateInput = normalizePdfmeTemplateInput(
    template.version.schemaJson,
    mapDocumentDataToTemplateInput(documentData as unknown as Record<string, unknown>, mappings)
  );
  const { template: renderTemplate, topicInputs } = buildPdfTopicTemplate(
    template.version.schemaJson as Template,
    documentData.topics.all
  );
  const mergedTemplateInput = {
    ...templateInput,
    ...topicInputs
  };

  documentData.pdfme.topic_inputs = topicInputs;

  return {
    documentData,
    template: {
      ...template,
      version: {
        ...template.version,
        schemaJson: renderTemplate
      }
    },
    mappings,
    templateInput: mergedTemplateInput
  };
}

export async function prepareApprovedQuotationSnapshot(
  quotationId: string,
  organizationId: string,
  userId: string
): Promise<ApprovedQuotationSnapshot> {
  const preview = await getQuotationDocumentPreviewData(quotationId, organizationId);
  const generatedAt = new Date().toISOString();

  return {
    quotationId,
    approvedAt: preview.documentData.approval.approvedAt,
    documentData: preview.documentData,
    templateVersionId: preview.template.version.id,
    templateInput: preview.templateInput,
    generatedAt,
    generatedBy: userId
  };
}
