import { env } from '@/lib/env';
import { logger } from '@/lib/logger';
import { getPortDocumensoConfig, type DocumensoApiVersion } from '@/lib/services/port-config';

interface DocumensoCreds {
  baseUrl: string;
  apiKey: string;
  apiVersion: DocumensoApiVersion;
}

async function resolveCreds(portId?: string): Promise<DocumensoCreds> {
  if (!portId) {
    return {
      baseUrl: env.DOCUMENSO_API_URL,
      apiKey: env.DOCUMENSO_API_KEY,
      apiVersion: env.DOCUMENSO_API_VERSION,
    };
  }
  const cfg = await getPortDocumensoConfig(portId);
  return { baseUrl: cfg.apiUrl, apiKey: cfg.apiKey, apiVersion: cfg.apiVersion };
}

async function documensoFetch(
  path: string,
  options?: RequestInit,
  portId?: string,
): Promise<unknown> {
  const { baseUrl, apiKey } = await resolveCreds(portId);
  const res = await fetch(`${baseUrl}${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      ...options?.headers,
    },
  });

  if (!res.ok) {
    const err = await res.text();
    logger.error({ path, status: res.status, err, portId }, 'Documenso API error');
    throw new Error(`Documenso API error: ${res.status}`);
  }

  return res.json();
}

// Documenso 2.x renamed top-level `id` → `documentId` and recipient `id` →
// `recipientId`; v1.13 still uses `id`. Normalize both shapes to the legacy
// `id` form that this codebase consumes everywhere downstream.
function normalizeDocument(raw: unknown): DocumensoDocument {
  const r = (raw ?? {}) as Record<string, unknown>;
  const id = String(r.documentId ?? r.id ?? '');
  const status = String(r.status ?? 'PENDING');
  const recipientsRaw = (r.recipients as Array<Record<string, unknown>> | undefined) ?? [];
  const recipients = recipientsRaw.map((rec) => ({
    id: String(rec.recipientId ?? rec.id ?? ''),
    name: String(rec.name ?? ''),
    email: String(rec.email ?? ''),
    role: String(rec.role ?? ''),
    signingOrder: Number(rec.signingOrder ?? 0),
    status: String(rec.signingStatus ?? rec.status ?? 'PENDING'),
    signingUrl: typeof rec.signingUrl === 'string' ? rec.signingUrl : undefined,
    embeddedUrl: typeof rec.embeddedUrl === 'string' ? rec.embeddedUrl : undefined,
  }));
  return { id, status, recipients };
}

export interface DocumensoRecipient {
  name: string;
  email: string;
  role: string;
  signingOrder: number;
}

export interface DocumensoDocument {
  id: string;
  status: string;
  recipients: Array<{
    id: string;
    name: string;
    email: string;
    role: string;
    signingOrder: number;
    status: string;
    signingUrl?: string;
    embeddedUrl?: string;
  }>;
}

export async function createDocument(
  title: string,
  pdfBase64: string,
  recipients: DocumensoRecipient[],
  portId?: string,
): Promise<DocumensoDocument> {
  return documensoFetch(
    '/api/v1/documents',
    {
      method: 'POST',
      body: JSON.stringify({ title, document: pdfBase64, recipients }),
    },
    portId,
  ).then(normalizeDocument);
}

export async function generateDocumentFromTemplate(
  templateId: number,
  payload: Record<string, unknown>,
  portId?: string,
): Promise<DocumensoDocument> {
  return documensoFetch(
    `/api/v1/templates/${templateId}/generate-document`,
    {
      method: 'POST',
      body: JSON.stringify(payload),
    },
    portId,
  ).then(normalizeDocument);
}

export async function sendDocument(docId: string, portId?: string): Promise<DocumensoDocument> {
  return documensoFetch(
    `/api/v1/documents/${docId}/send`,
    {
      method: 'POST',
    },
    portId,
  ).then(normalizeDocument);
}

export async function getDocument(docId: string, portId?: string): Promise<DocumensoDocument> {
  return documensoFetch(`/api/v1/documents/${docId}`, undefined, portId).then(normalizeDocument);
}

export async function sendReminder(
  docId: string,
  signerId: string,
  portId?: string,
): Promise<void> {
  await documensoFetch(
    `/api/v1/documents/${docId}/recipients/${signerId}/remind`,
    {
      method: 'POST',
    },
    portId,
  );
}

export async function downloadSignedPdf(docId: string, portId?: string): Promise<Buffer> {
  const { baseUrl, apiKey } = await resolveCreds(portId);
  const res = await fetch(`${baseUrl}/api/v1/documents/${docId}/download`, {
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (!res.ok) {
    const err = await res.text();
    logger.error({ docId, status: res.status, err, portId }, 'Documenso download error');
    throw new Error(`Documenso download error: ${res.status}`);
  }

  const arrayBuffer = await res.arrayBuffer();
  return Buffer.from(arrayBuffer);
}

/** Convenience health-check used by the admin "Test connection" button. */
export async function checkDocumensoHealth(
  portId?: string,
): Promise<{ ok: boolean; status?: number; error?: string }> {
  try {
    const { baseUrl, apiKey } = await resolveCreds(portId);
    const res = await fetch(`${baseUrl}/api/v1/health`, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });
    return { ok: res.ok, status: res.status };
  } catch (err) {
    return { ok: false, error: err instanceof Error ? err.message : 'Unknown error' };
  }
}

// ─── Version-aware abstractions (Phase A PR2) ─────────────────────────────────
//
// Documenso v1.13 and v2.x diverge on field placement and document deletion:
//
//   v1.13: per-field POST /api/v1/documents/{id}/fields with PIXEL coords;
//          DELETE /api/v1/documents/{id} for void.
//   v2.x:  bulk   POST /api/v2/envelope/field/create-many with PERCENT
//          coords (0-100) and rich `fieldMeta`;
//          DELETE /api/v2/envelope/{id} for void.
//
// Callers always work in PERCENT (0-100). For v1 the abstraction multiplies by
// the page dimensions returned by Documenso (cached per docId for the lifetime
// of the process — fields for a given doc usually go in a single batch).

export type DocumensoFieldType = 'SIGNATURE' | 'INITIALS' | 'DATE' | 'TEXT' | 'EMAIL';

export interface DocumensoFieldPlacement {
  /** Documenso recipient id; v1 expects number, v2 string — coerced internally. */
  recipientId: number | string;
  type: DocumensoFieldType;
  pageNumber: number;
  /** All four are 0-100 percent of page dimensions. */
  pageX: number;
  pageY: number;
  pageWidth: number;
  pageHeight: number;
  /** Optional v2 fieldMeta — passed through verbatim, ignored on v1. */
  fieldMeta?: Record<string, unknown>;
}

export interface DocumensoPageDimensions {
  width: number;
  height: number;
}

const DEFAULT_PAGE_DIMENSIONS: DocumensoPageDimensions = { width: 595, height: 842 }; // A4 pt

const pageDimensionCache = new Map<string, DocumensoPageDimensions>();

/** Test seam — clears the page-dimension memoization. */
export function __resetDocumensoCachesForTests(): void {
  pageDimensionCache.clear();
}

async function getPageDimensions(docId: string, portId?: string): Promise<DocumensoPageDimensions> {
  const cached = pageDimensionCache.get(docId);
  if (cached) return cached;
  // v1 doesn't expose page dimensions cleanly via the public API; the auto-
  // placement use case is footer-anchored signature fields, where a default A4
  // page rendered by Documenso is a safe assumption. Real page dims can be
  // wired in a follow-up by parsing the document/document-data endpoints.
  void portId;
  pageDimensionCache.set(docId, DEFAULT_PAGE_DIMENSIONS);
  return DEFAULT_PAGE_DIMENSIONS;
}

/**
 * Place one or more fields on a Documenso document. Coordinates are PERCENT
 * (0-100) and converted to pixels for v1 internally.
 *
 * v1: dispatches one POST per field (no bulk endpoint).
 * v2: single bulk POST.
 */
export async function placeFields(
  docId: string,
  fields: DocumensoFieldPlacement[],
  portId?: string,
): Promise<void> {
  if (fields.length === 0) return;
  const { baseUrl, apiKey, apiVersion } = await resolveCreds(portId);

  if (apiVersion === 'v2') {
    const v2Fields = fields.map((f) => ({
      recipientId: String(f.recipientId),
      type: f.type,
      pageNumber: f.pageNumber,
      positionX: f.pageX,
      positionY: f.pageY,
      width: f.pageWidth,
      height: f.pageHeight,
      ...(f.fieldMeta ? { fieldMeta: f.fieldMeta } : {}),
    }));
    // Note: v2 endpoint shape (envelopeId/recipientId types) must be
    // confirmed against a live Documenso 2.x instance — see PR11 realapi
    // suite. Spec risk register flags this drift as the top v2 risk.
    const res = await fetch(`${baseUrl}/api/v2/envelope/field/create-many`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ envelopeId: docId, fields: v2Fields }),
    });
    if (!res.ok) {
      const err = await res.text();
      logger.error({ docId, status: res.status, err, portId }, 'Documenso v2 placeFields error');
      throw new Error(`Documenso v2 placeFields error: ${res.status}`);
    }
    return;
  }

  const dims = await getPageDimensions(docId, portId);
  for (const f of fields) {
    const body = {
      recipientId: typeof f.recipientId === 'string' ? Number(f.recipientId) : f.recipientId,
      type: f.type,
      pageNumber: f.pageNumber,
      pageX: Math.round((f.pageX / 100) * dims.width),
      pageY: Math.round((f.pageY / 100) * dims.height),
      pageWidth: Math.round((f.pageWidth / 100) * dims.width),
      pageHeight: Math.round((f.pageHeight / 100) * dims.height),
    };
    const res = await fetch(`${baseUrl}/api/v1/documents/${docId}/fields`, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    });
    if (!res.ok) {
      const err = await res.text();
      logger.error({ docId, status: res.status, err, portId }, 'Documenso v1 placeField error');
      throw new Error(`Documenso v1 placeField error: ${res.status}`);
    }
  }
}

/**
 * Auto-position one SIGNATURE field per recipient at the last-page footer,
 * staggered horizontally so multiple signers don't overlap. Used by the
 * upload-path wizard — admins can refine in Documenso afterwards.
 *
 * Layout (percent of page):
 *   y       = 88 (footer band)
 *   height  = 6
 *   width   = min(20, 80 / N)
 *   x       = i * (80/N) + (40 - 80/N * N / 2)  (centered row)
 */
export async function placeDefaultSignatureFields(
  docId: string,
  recipients: Array<{ id: number | string; pageNumber: number }>,
  portId?: string,
): Promise<void> {
  if (recipients.length === 0) return;
  const fields: DocumensoFieldPlacement[] = computeDefaultSignatureLayout(recipients);
  await placeFields(docId, fields, portId);
}

/** Pure function exported for unit testing layout math. */
export function computeDefaultSignatureLayout(
  recipients: Array<{ id: number | string; pageNumber: number }>,
): DocumensoFieldPlacement[] {
  const n = recipients.length;
  if (n === 0) return [];
  const slot = Math.min(20, 80 / n); // percent width per signer
  const rowWidth = slot * n;
  const startX = 50 - rowWidth / 2;
  return recipients.map((r, i) => ({
    recipientId: r.id,
    type: 'SIGNATURE',
    pageNumber: r.pageNumber,
    pageX: Math.max(0, startX + i * slot),
    pageY: 88,
    pageWidth: slot,
    pageHeight: 6,
  }));
}

/**
 * Void/cancel a Documenso document.
 *
 * v1: DELETE /api/v1/documents/{id}
 * v2: DELETE /api/v2/envelope/{id}
 *
 * Idempotent on 404 (already gone) — logs and resolves.
 */
export async function voidDocument(docId: string, portId?: string): Promise<void> {
  const { baseUrl, apiKey, apiVersion } = await resolveCreds(portId);
  const path = apiVersion === 'v2' ? `/api/v2/envelope/${docId}` : `/api/v1/documents/${docId}`;
  const res = await fetch(`${baseUrl}${path}`, {
    method: 'DELETE',
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  if (res.status === 404) {
    logger.warn({ docId, portId }, 'Documenso voidDocument: already deleted');
    return;
  }
  if (!res.ok) {
    const err = await res.text();
    logger.error({ docId, status: res.status, err, portId }, 'Documenso voidDocument error');
    throw new Error(`Documenso voidDocument error: ${res.status}`);
  }
}
