const fs = require('node:fs');
const path = require('node:path');
const postgres = require('postgres');

const FIXTURE_USERS = {
  superAdmin: process.env.SUPER_ADMIN_EMAIL || 'admin@allaos.com',
  admin: 'task-h1-admin@local.test',
  pdfUser: 'task-h1-pdf-user@local.test',
  viewer: 'task-h1-viewer@local.test'
};

const PASSWORDS = {
  superAdmin: process.env.SUPER_ADMIN_PASSWORD || 'A3YH2Xflw1@allaos',
  fixture: 'TaskH1Pass!2026'
};

function loadEnvFile(filePath) {
  if (!fs.existsSync(filePath)) {
    return;
  }

  const content = fs.readFileSync(filePath, 'utf8');

  for (const rawLine of content.split(/\r?\n/)) {
    const line = rawLine.trim();

    if (!line || line.startsWith('#')) {
      continue;
    }

    const separatorIndex = line.indexOf('=');

    if (separatorIndex === -1) {
      continue;
    }

    const key = line.slice(0, separatorIndex).trim();
    let value = line.slice(separatorIndex + 1).trim();

    if (
      (value.startsWith('"') && value.endsWith('"')) ||
      (value.startsWith("'") && value.endsWith("'"))
    ) {
      value = value.slice(1, -1);
    }

    if (!(key in process.env)) {
      process.env[key] = value;
    }
  }
}

function loadLocalEnv() {
  loadEnvFile(path.resolve(process.cwd(), '.env.local'));
  loadEnvFile(path.resolve(process.cwd(), '.env'));
}

function requireEnv(name) {
  const value = process.env[name]?.trim();

  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }

  return value;
}

function assert(condition, message) {
  if (!condition) {
    throw new Error(message);
  }
}

function extractCookies(response) {
  const cookieMap = new Map();
  const setCookies = typeof response.headers.getSetCookie === 'function'
    ? response.headers.getSetCookie()
    : response.headers.get('set-cookie')
      ? [response.headers.get('set-cookie')]
      : [];

  for (const rawCookie of setCookies) {
    const [pair] = rawCookie.split(';', 1);
    const separatorIndex = pair.indexOf('=');

    if (separatorIndex === -1) {
      continue;
    }

    cookieMap.set(pair.slice(0, separatorIndex), pair.slice(separatorIndex + 1));
  }

  return cookieMap;
}

function mergeCookieMaps(base, next) {
  const merged = new Map(base);

  for (const [key, value] of next.entries()) {
    merged.set(key, value);
  }

  return merged;
}

function toCookieHeader(cookieMap) {
  return [...cookieMap.entries()].map(([key, value]) => `${key}=${value}`).join('; ');
}

async function signIn(baseUrl, email, password) {
  const csrfResponse = await fetch(`${baseUrl}/api/auth/csrf`);
  assert(csrfResponse.ok, `Unable to fetch CSRF token for ${email}`);
  const csrfPayload = await csrfResponse.json();
  const cookies = extractCookies(csrfResponse);
  const body = new URLSearchParams({
    email,
    password,
    csrfToken: csrfPayload.csrfToken,
    callbackUrl: `${baseUrl}/dashboard`,
    json: 'true'
  });
  const signInResponse = await fetch(`${baseUrl}/api/auth/callback/credentials?json=true`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Cookie: toCookieHeader(cookies)
    },
    body: body.toString(),
    redirect: 'manual'
  });

  assert(
    signInResponse.status === 200 || signInResponse.status === 302,
    `Unexpected sign-in status ${signInResponse.status} for ${email}`
  );

  const mergedCookies = mergeCookieMaps(cookies, extractCookies(signInResponse));
  assert(mergedCookies.size > 0, `Sign-in did not return cookies for ${email}`);

  return mergedCookies;
}

async function fetchWithAuth(baseUrl, cookieMap, pathname, init = {}) {
  const response = await fetch(`${baseUrl}${pathname}`, {
    ...init,
    headers: {
      ...(init.headers || {}),
      Cookie: toCookieHeader(cookieMap)
    },
    redirect: 'manual'
  });
  const bytes = Buffer.from(await response.arrayBuffer());
  const contentType = response.headers.get('content-type');
  const disposition = response.headers.get('content-disposition');
  let json = null;

  if (contentType?.includes('application/json')) {
    json = JSON.parse(bytes.toString('utf8'));
  }

  return {
    status: response.status,
    contentType,
    disposition,
    bytes,
    json
  };
}

function estimatePageCount(buffer) {
  const text = buffer.toString('latin1');

  return {
    startsWithPdf: text.startsWith('%PDF-'),
    byteLength: buffer.length
  };
}

async function queryFixtureState(sql, approvedQuotationId) {
  const quotation =
    (
      await sql`
        select
          id,
          organization_id,
          code,
          approved_at,
          approved_pdf_url,
          approved_snapshot,
          approved_template_version_id
        from crm_quotations
        where id = ${approvedQuotationId}
        limit 1
      `
    )[0] ?? null;

  assert(quotation, 'Approved quotation fixture not found in DB');

  const [attachment, audit] = await Promise.all([
    sql`
      select
        id,
        file_name,
        file_path,
        file_type,
        file_size,
        description
      from crm_quotation_attachments
      where quotation_id = ${approvedQuotationId}
        and deleted_at is null
      order by created_at desc
      limit 1
    `.then((rows) => rows[0] ?? null),
    sql`
      select
        id,
        entity_type,
        entity_id,
        action,
        created_at
      from tr_audit_logs
      where organization_id = ${quotation.organization_id}
        and entity_type = 'crm_quotation_pdf_generate_approved'
        and entity_id = ${approvedQuotationId}
      order by created_at desc
      limit 1
    `.then((rows) => rows[0] ?? null)
  ]);

  return {
    quotation,
    attachment,
    audit
  };
}

async function queryTemplatePageCount(sql, templateVersionId) {
  const version =
    (
      await sql`
        select schema_json
        from crm_document_template_versions
        where id = ${templateVersionId}
        limit 1
      `
    )[0] ?? null;

  const schemaJson =
    typeof version?.schema_json === 'string'
      ? JSON.parse(version.schema_json)
      : version?.schema_json ?? null;
  const schemas = schemaJson?.schemas;

  return Array.isArray(schemas) ? schemas.length : 0;
}

async function main() {
  loadLocalEnv();

  const baseUrl = process.env.APP_URL || 'http://localhost:3000';
  const sql = postgres(requireEnv('DATABASE_URL'), { prepare: false });

  try {
    const approvedQuotation =
      (
        await sql`
          select id, code
          from crm_quotations
          where code = 'QT-TASK-H1-APPROVED'
          limit 1
        `
      )[0] ?? null;
    const draftQuotation =
      (
        await sql`
          select id, code
          from crm_quotations
          where code = 'QT-TASK-H1-DRAFT'
          limit 1
        `
      )[0] ?? null;

    assert(approvedQuotation, 'Approved fixture quotation is missing');
    assert(draftQuotation, 'Draft fixture quotation is missing');

    const superAdminCookies = await signIn(baseUrl, FIXTURE_USERS.superAdmin, PASSWORDS.superAdmin);
    const adminCookies = await signIn(baseUrl, FIXTURE_USERS.admin, PASSWORDS.fixture);
    const pdfUserCookies = await signIn(baseUrl, FIXTURE_USERS.pdfUser, PASSWORDS.fixture);
    const viewerCookies = await signIn(baseUrl, FIXTURE_USERS.viewer, PASSWORDS.fixture);

    const preview = await fetchWithAuth(
      baseUrl,
      superAdminCookies,
      `/api/crm/quotations/${approvedQuotation.id}/pdf-preview`
    );
    assert(preview.status === 200, `Preview API failed with ${preview.status}`);
    assert(preview.contentType?.includes('application/pdf'), 'Preview did not return PDF');
    assert(preview.disposition?.startsWith('inline;'), 'Preview was not inline');

    const download = await fetchWithAuth(
      baseUrl,
      superAdminCookies,
      `/api/crm/quotations/${approvedQuotation.id}/pdf-download`
    );
    assert(download.status === 200, `Download API failed with ${download.status}`);
    assert(download.contentType?.includes('application/pdf'), 'Download did not return PDF');
    assert(download.disposition?.startsWith('attachment;'), 'Download was not attachment');

    const generateApproved = await fetchWithAuth(
      baseUrl,
      superAdminCookies,
      `/api/crm/quotations/${approvedQuotation.id}/approved-pdf`,
      { method: 'POST' }
    );
    assert(
      generateApproved.status === 200,
      `Approved PDF POST failed with ${generateApproved.status}`
    );
    assert(
      generateApproved.disposition?.startsWith('attachment;'),
      'Approved PDF POST was not attachment'
    );

    const approvedGet = await fetchWithAuth(
      baseUrl,
      superAdminCookies,
      `/api/crm/quotations/${approvedQuotation.id}/approved-pdf`
    );
    assert(approvedGet.status === 200, `Approved PDF GET failed with ${approvedGet.status}`);
    assert(approvedGet.disposition?.startsWith('inline;'), 'Approved PDF GET was not inline');

    const nonApprovedGenerate = await fetchWithAuth(
      baseUrl,
      superAdminCookies,
      `/api/crm/quotations/${draftQuotation.id}/approved-pdf`,
      { method: 'POST' }
    );
    const previewData = await fetchWithAuth(
      baseUrl,
      superAdminCookies,
      `/api/crm/quotations/${approvedQuotation.id}/document-preview`
    );
    assert(
      nonApprovedGenerate.status === 400,
      `Draft quotation POST expected 400, got ${nonApprovedGenerate.status}`
    );
    assert(previewData.status === 200, `Document preview expected 200, got ${previewData.status}`);

    const adminPreview = await fetchWithAuth(
      baseUrl,
      adminCookies,
      `/api/crm/quotations/${approvedQuotation.id}/pdf-preview`
    );
    const pdfUserPreview = await fetchWithAuth(
      baseUrl,
      pdfUserCookies,
      `/api/crm/quotations/${approvedQuotation.id}/pdf-preview`
    );
    const viewerPreview = await fetchWithAuth(
      baseUrl,
      viewerCookies,
      `/api/crm/quotations/${approvedQuotation.id}/pdf-preview`
    );
    const viewerGenerate = await fetchWithAuth(
      baseUrl,
      viewerCookies,
      `/api/crm/quotations/${approvedQuotation.id}/approved-pdf`,
      { method: 'POST' }
    );

    assert(adminPreview.status === 200, `Admin preview expected 200, got ${adminPreview.status}`);
    assert(
      pdfUserPreview.status === 200,
      `Regular permitted user preview expected 200, got ${pdfUserPreview.status}`
    );
    assert(
      viewerPreview.status === 403,
      `Regular user without preview permission expected 403, got ${viewerPreview.status}`
    );
    assert(
      viewerGenerate.status === 403,
      `Regular user without generate permission expected 403, got ${viewerGenerate.status}`
    );

    const persistence = await queryFixtureState(sql, approvedQuotation.id);
    assert(persistence.quotation.approved_pdf_url, 'approvedPdfUrl was not saved');
    assert(
      persistence.quotation.approved_template_version_id,
      'approvedTemplateVersionId was not saved'
    );
    assert(persistence.quotation.approved_snapshot, 'approvedSnapshot was not saved');
    assert(persistence.attachment, 'Approved PDF attachment metadata was not created');
    assert(persistence.audit, 'Approved PDF audit log was not created');

    const absoluteStoredPath = path.join(
      process.cwd(),
      'public',
      persistence.quotation.approved_pdf_url.replace(/^\//, '')
    );
    assert(fs.existsSync(absoluteStoredPath), 'Stored approved PDF file is missing on disk');

    const pdfShape = estimatePageCount(approvedGet.bytes);
    const approvedSnapshot = persistence.quotation.approved_snapshot;
    const previewPayload = previewData.json?.preview;
    const templatePageCount = await queryTemplatePageCount(
      sql,
      persistence.quotation.approved_template_version_id
    );

    assert(pdfShape.startsWithPdf, 'Approved PDF response does not start with %PDF header');
    assert(pdfShape.byteLength > 1000, `Approved PDF size looks too small: ${pdfShape.byteLength}`);
    assert(templatePageCount >= 1, `Template page count is not reasonable: ${templatePageCount}`);
    assert(
      approvedSnapshot.documentData?.customer?.name,
      'approvedSnapshot is missing customer block data'
    );
    assert(
      approvedSnapshot.documentData?.quotation?.code === approvedQuotation.code,
      'approvedSnapshot is missing quotation code'
    );
    assert(
      Array.isArray(approvedSnapshot.documentData?.items) &&
        approvedSnapshot.documentData.items.length >= 1,
      'approvedSnapshot is missing item table data'
    );
    assert(
      typeof approvedSnapshot.documentData?.quotation?.totalAmount === 'number',
      'approvedSnapshot is missing total amount'
    );
    assert(
      approvedSnapshot.documentData?.topics?.scope?.length >= 1,
      'approvedSnapshot is missing scope topic data'
    );
    assert(
      approvedSnapshot.documentData?.topics?.exclusion?.length >= 1,
      'approvedSnapshot is missing exclusion topic data'
    );
    assert(
      approvedSnapshot.documentData?.topics?.payment?.length >= 1,
      'approvedSnapshot is missing payment topic data'
    );
    assert(
      typeof previewPayload?.templateInput?.quotation_price === 'string',
      'templateInput quotation_price is not formatted string'
    );
    assert(
      typeof previewPayload?.templateInput?.quotation_date === 'string',
      'templateInput quotation_date is not formatted string'
    );
    assert(
      Array.isArray(previewPayload?.templateInput?.exclusion_data) &&
        previewPayload.templateInput.exclusion_data.length >= 1 &&
        Array.isArray(previewPayload.templateInput.exclusion_data[0]),
      'templateInput exclusion_data is not a non-empty string[][] table'
    );
    const dynamicTopicKeys = Object.keys(previewPayload?.templateInput ?? {}).filter(
      (key) => key.startsWith('topic_') || key.startsWith('item_topic_')
    );
    assert(dynamicTopicKeys.length >= 2, 'templateInput is missing dynamic topic keys');
    assert(
      Array.isArray(previewPayload?.documentData?.pdfme?.topic_inputs?.[dynamicTopicKeys[0]]),
      'documentData pdfme.topic_inputs is missing generated topic inputs'
    );
    const approvedPdfText = approvedGet.bytes.toString('latin1');
    assert(
      !approvedPdfText.includes('{exclusion_data}'),
      'Approved PDF still contains unresolved {exclusion_data}'
    );
    assert(
      !approvedPdfText.includes('{quotation_price}'),
      'Approved PDF still contains unresolved {quotation_price}'
    );
    assert(
      !approvedPdfText.includes('{item_topic}'),
      'Approved PDF still contains unresolved {item_topic}'
    );
    assert(
      approvedSnapshot.documentData?.approval?.approvers?.length >= 1,
      'approvedSnapshot is missing approval block data'
    );
    assert(
      Object.values(approvedSnapshot.documentData?.signatures ?? {}).some(Boolean),
      'approvedSnapshot is missing signature placeholder data'
    );

    console.log(
      JSON.stringify(
        {
          smokeTestRecord: {
            approvedQuotationId: approvedQuotation.id,
            approvedQuotationCode: approvedQuotation.code,
            draftQuotationId: draftQuotation.id,
            draftQuotationCode: draftQuotation.code
          },
          apiChecks: {
            preview: {
              status: preview.status,
              disposition: preview.disposition,
              bytes: preview.bytes.length
            },
            download: {
              status: download.status,
              disposition: download.disposition,
              bytes: download.bytes.length
            },
            approvedPost: {
              status: generateApproved.status,
              disposition: generateApproved.disposition,
              bytes: generateApproved.bytes.length
            },
            approvedGet: {
              status: approvedGet.status,
              disposition: approvedGet.disposition,
              bytes: approvedGet.bytes.length
            },
            documentPreview: {
              status: previewData.status,
              quotationPrice: previewPayload?.templateInput?.quotation_price ?? null,
              quotationDate: previewPayload?.templateInput?.quotation_date ?? null,
              exclusionRows: Array.isArray(previewPayload?.templateInput?.exclusion_data)
                ? previewPayload.templateInput.exclusion_data.length
                : 0,
              dynamicTopicKeys
            },
            nonApprovedPost: {
              status: nonApprovedGenerate.status,
              message: nonApprovedGenerate.json?.message ?? null
            }
          },
          permissionChecks: {
            superAdminPreview: preview.status,
            adminPreview: adminPreview.status,
            regularUserWithPermissionPreview: pdfUserPreview.status,
            regularUserWithoutPermissionPreview: viewerPreview.status,
            regularUserWithoutPermissionGenerate: viewerGenerate.status
          },
          persistence: {
            approvedPdfUrl: persistence.quotation.approved_pdf_url,
            approvedTemplateVersionId: persistence.quotation.approved_template_version_id,
            attachmentId: persistence.attachment.id,
            auditId: persistence.audit.id,
            storedFileExists: true
          },
          outputValidation: {
            templatePageCount,
            pdfByteLength: pdfShape.byteLength,
            startsWithPdfHeader: pdfShape.startsWithPdf,
            customerName: approvedSnapshot.documentData.customer.name,
            quotationCode: approvedSnapshot.documentData.quotation.code,
            itemCount: approvedSnapshot.documentData.items.length,
            totalAmount: approvedSnapshot.documentData.quotation.totalAmount,
            scopeTopicCount: approvedSnapshot.documentData.topics.scope.length,
            exclusionTopicCount: approvedSnapshot.documentData.topics.exclusion.length,
            paymentTopicCount: approvedSnapshot.documentData.topics.payment.length,
            approvalStepCount: approvedSnapshot.documentData.approval.approvers.length
          }
        },
        null,
        2
      )
    );
  } finally {
    await sql.end();
  }
}

main().catch((error) => {
  console.error('[verify-task-h1] Failed:', error.message);
  process.exitCode = 1;
});
