/**
 * Builds the structured payload that becomes the JSON + HTML inside a
 * GDPR client-data export. Pure read-side — no writes, no I/O outside
 * Drizzle. The worker pairs this with the actual ZIP/upload/email work.
 *
 * GDPR Article 15 (right of access) requires that we hand the data
 * subject everything we hold about them. This builder enumerates every
 * table that carries a `clientId` foreign key plus the polymorphic
 * yacht ownership rows that resolve to this client.
 */

import { and, eq, or } from 'drizzle-orm';

import { db } from '@/lib/db';
import { NotFoundError } from '@/lib/errors';
import {
  clients,
  clientContacts,
  clientAddresses,
  clientNotes,
  clientRelationships,
  clientTags,
} from '@/lib/db/schema/clients';
import { tags } from '@/lib/db/schema/system';
import { companies, companyMemberships } from '@/lib/db/schema/companies';
import { yachts } from '@/lib/db/schema/yachts';
import { interests } from '@/lib/db/schema/interests';
import { berthReservations } from '@/lib/db/schema/reservations';
import { invoices } from '@/lib/db/schema/financial';
import { documents } from '@/lib/db/schema/documents';
import { auditLogs } from '@/lib/db/schema/system';

export interface GdprBundle {
  /** Bundle metadata for traceability. */
  meta: {
    generatedAt: string;
    portId: string;
    clientId: string;
    schemaVersion: 1;
  };
  client: Record<string, unknown>;
  contacts: Record<string, unknown>[];
  addresses: Record<string, unknown>[];
  tags: Array<{ id: string; name: string; color: string }>;
  relationships: Record<string, unknown>[];
  notes: Record<string, unknown>[];
  ownedYachts: Record<string, unknown>[];
  companyMemberships: Array<{
    membership: Record<string, unknown>;
    company: Record<string, unknown>;
  }>;
  interests: Record<string, unknown>[];
  reservations: Record<string, unknown>[];
  invoices: Record<string, unknown>[];
  documents: Record<string, unknown>[];
  auditTrail: Record<string, unknown>[];
}

/**
 * Loads every row that references this client across all tenant-scoped
 * tables. Every query is filtered by `portId` as well, so a stale FK
 * to another tenant never leaks across.
 */
export async function buildClientBundle(clientId: string, portId: string): Promise<GdprBundle> {
  const client = await db.query.clients.findFirst({ where: eq(clients.id, clientId) });
  if (!client || client.portId !== portId) {
    throw new NotFoundError('Client');
  }

  const [
    contacts,
    addresses,
    relationships,
    notes,
    tagJoins,
    ownedYachts,
    membershipRows,
    interestRows,
    reservationRows,
    invoiceRows,
    documentRows,
    auditRows,
  ] = await Promise.all([
    db.query.clientContacts.findMany({ where: eq(clientContacts.clientId, clientId) }),
    db.query.clientAddresses.findMany({ where: eq(clientAddresses.clientId, clientId) }),
    db.query.clientRelationships.findMany({
      where: or(
        eq(clientRelationships.clientAId, clientId),
        eq(clientRelationships.clientBId, clientId),
      ),
    }),
    db.query.clientNotes.findMany({ where: eq(clientNotes.clientId, clientId) }),
    db
      .select({
        id: tags.id,
        name: tags.name,
        color: tags.color,
      })
      .from(clientTags)
      .innerJoin(tags, eq(clientTags.tagId, tags.id))
      .where(eq(clientTags.clientId, clientId)),
    db.query.yachts.findMany({
      where: and(
        eq(yachts.portId, portId),
        eq(yachts.currentOwnerType, 'client'),
        eq(yachts.currentOwnerId, clientId),
      ),
    }),
    db
      .select({ membership: companyMemberships, company: companies })
      .from(companyMemberships)
      .innerJoin(companies, eq(companyMemberships.companyId, companies.id))
      .where(and(eq(companyMemberships.clientId, clientId), eq(companies.portId, portId))),
    db.query.interests.findMany({
      where: and(eq(interests.clientId, clientId), eq(interests.portId, portId)),
    }),
    db.query.berthReservations.findMany({
      where: and(eq(berthReservations.clientId, clientId), eq(berthReservations.portId, portId)),
    }),
    db.query.invoices.findMany({
      where: and(
        eq(invoices.portId, portId),
        eq(invoices.billingEntityType, 'client'),
        eq(invoices.billingEntityId, clientId),
      ),
    }),
    db.query.documents.findMany({
      where: and(eq(documents.portId, portId), eq(documents.clientId, clientId)),
    }),
    db.query.auditLogs.findMany({
      where: and(
        eq(auditLogs.portId, portId),
        eq(auditLogs.entityType, 'client'),
        eq(auditLogs.entityId, clientId),
      ),
      orderBy: (t, { desc }) => [desc(t.createdAt)],
      limit: 500,
    }),
  ]);

  return {
    meta: {
      generatedAt: new Date().toISOString(),
      portId,
      clientId,
      schemaVersion: 1,
    },
    client: client as unknown as Record<string, unknown>,
    contacts: contacts as unknown as Record<string, unknown>[],
    addresses: addresses as unknown as Record<string, unknown>[],
    tags: tagJoins,
    relationships: relationships as unknown as Record<string, unknown>[],
    notes: notes as unknown as Record<string, unknown>[],
    ownedYachts: ownedYachts as unknown as Record<string, unknown>[],
    companyMemberships: membershipRows as unknown as Array<{
      membership: Record<string, unknown>;
      company: Record<string, unknown>;
    }>,
    interests: interestRows as unknown as Record<string, unknown>[],
    reservations: reservationRows as unknown as Record<string, unknown>[],
    invoices: invoiceRows as unknown as Record<string, unknown>[],
    documents: documentRows as unknown as Record<string, unknown>[],
    auditTrail: auditRows as unknown as Record<string, unknown>[],
  };
}

// ─── HTML rendering ──────────────────────────────────────────────────────────

function escapeHtml(s: unknown): string {
  if (s === null || s === undefined) return '';
  return String(s)
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function tableSection(title: string, rows: Record<string, unknown>[]): string {
  if (rows.length === 0) {
    return `<section><h2>${escapeHtml(title)}</h2><p class="empty">No records.</p></section>`;
  }
  const headers = Array.from(
    rows.reduce<Set<string>>((set, r) => {
      Object.keys(r).forEach((k) => set.add(k));
      return set;
    }, new Set()),
  );
  const headerHtml = headers.map((h) => `<th>${escapeHtml(h)}</th>`).join('');
  const bodyHtml = rows
    .map(
      (r) =>
        `<tr>${headers
          .map((h) => {
            const v = r[h];
            const cell = typeof v === 'object' && v !== null ? JSON.stringify(v) : v;
            return `<td>${escapeHtml(cell)}</td>`;
          })
          .join('')}</tr>`,
    )
    .join('');
  return `
    <section>
      <h2>${escapeHtml(title)} <small>(${rows.length})</small></h2>
      <table><thead><tr>${headerHtml}</tr></thead><tbody>${bodyHtml}</tbody></table>
    </section>
  `;
}

/**
 * Renders the bundle as a self-contained HTML document — no external
 * resources, no JS — so it opens in any browser including offline.
 */
export function renderBundleHtml(bundle: GdprBundle): string {
  const clientName = String(bundle.client.fullName ?? bundle.meta.clientId ?? 'Unknown');
  const sections = [
    tableSection('Client', [bundle.client]),
    tableSection('Contacts', bundle.contacts),
    tableSection('Addresses', bundle.addresses),
    tableSection('Tags', bundle.tags as unknown as Record<string, unknown>[]),
    tableSection('Relationships', bundle.relationships),
    tableSection('Notes', bundle.notes),
    tableSection('Owned yachts', bundle.ownedYachts),
    tableSection(
      'Company memberships',
      bundle.companyMemberships.map((m) => ({
        ...m.membership,
        companyName: m.company.name,
        companyLegalName: m.company.legalName,
      })),
    ),
    tableSection('Interests', bundle.interests),
    tableSection('Reservations', bundle.reservations),
    tableSection('Invoices', bundle.invoices),
    tableSection('Documents', bundle.documents),
    tableSection('Audit trail (last 500 events)', bundle.auditTrail),
  ].join('\n');

  return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Personal data export — ${escapeHtml(clientName)}</title>
<style>
  body { font: 14px/1.5 -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif; margin: 2rem; max-width: 1200px; }
  h1 { border-bottom: 2px solid #333; padding-bottom: 0.5rem; }
  h2 { color: #1a3a5c; margin-top: 2rem; }
  small { font-weight: normal; color: #666; }
  .empty { color: #888; font-style: italic; }
  table { width: 100%; border-collapse: collapse; margin: 0.5rem 0; font-size: 12px; }
  th, td { border: 1px solid #ddd; padding: 4px 8px; vertical-align: top; word-break: break-word; }
  th { background: #f0f4f8; text-align: left; }
  tr:nth-child(even) { background: #fafbfc; }
  .meta { background: #f0f4f8; padding: 1rem; border-radius: 4px; }
</style>
</head>
<body>
  <h1>Personal data export</h1>
  <div class="meta">
    <div><strong>Client:</strong> ${escapeHtml(clientName)} <code>(${escapeHtml(bundle.meta.clientId)})</code></div>
    <div><strong>Generated:</strong> ${escapeHtml(bundle.meta.generatedAt)}</div>
    <div><strong>Schema version:</strong> ${escapeHtml(bundle.meta.schemaVersion)}</div>
  </div>
  ${sections}
</body>
</html>`;
}
