/**
 * Contract renewal & amendment query helpers — wave-12.
 *
 * renew:   creates a new DRAFT contract linked via renewed_from_id, clones signatories
 *          with fresh tokens.
 * amend:   creates a new DRAFT contract linked via amended_from_id, clones signatories
 *          with fresh tokens, pre-fills with original content.
 * lineage: fetches the chain of renewal/amendment ancestors for a contract.
 * expiry:  lists SIGNED contracts with expiry_date within N days (for cron).
 *
 * Every write is inside db.transaction() with a paired audit_log insert.
 * Signatory tokens are freshly generated via crypto.subtle (Web Crypto) — originals
 * are NEVER reused because contract_signatories.token is NOT NULL UNIQUE.
 */
import { and, eq, desc, sql } from 'drizzle-orm'
import type { Db } from '../client'
import { contracts } from '../schema/contracts'
import { contractSignatories } from '../schema/contracts'
import type { ContractRow } from '../schema/contracts'
import { auditLog } from './_audit-forward'

// ── Public interfaces ─────────────────────────────────────────────────────────

export interface RenewalContract {
  id: string
  tenantId: string
  customerId: string | null
  title: string
  content: string
  status: 'draft' | 'sent' | 'signed' | 'voided'
  renewedFromId: string | null
  amendedFromId: string | null
  effectiveDate: string | null
  expiryDate: string | null
  signerEmail: string | null
  signerName: string | null
  metadata: unknown
  createdAt: string
  updatedAt: string
}

export interface ContractLineageItem {
  id: string
  title: string
  status: 'draft' | 'sent' | 'signed' | 'voided'
  renewedFromId: string | null
  amendedFromId: string | null
  effectiveDate: string | null
  expiryDate: string | null
  createdAt: string
}

export interface ContractLineage {
  current: ContractLineageItem
  ancestors: ContractLineageItem[]
  renewals: ContractLineageItem[]
  amendments: ContractLineageItem[]
}

export interface ExpiringContract {
  id: string
  tenantId: string
  title: string
  expiryDate: string
  signerEmail: string | null
}

// ── Token generation ──────────────────────────────────────────────────────────

/** Generate a 32-byte URL-safe opaque token via Web Crypto (CF Workers compatible). */
async function generateSignatoryToken(): Promise<string> {
  const buf = new Uint8Array(32)
  crypto.getRandomValues(buf)
  return Array.from(buf)
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('')
}

// ── Serializer ────────────────────────────────────────────────────────────────

function serializeRow(row: ContractRow): RenewalContract {
  return {
    id: row.id,
    tenantId: row.tenantId,
    customerId: row.customerId ?? null,
    title: row.title,
    content: row.content,
    status: row.status as RenewalContract['status'],
    renewedFromId: (row as ContractRow & { renewedFromId?: string | null }).renewedFromId ?? null,
    amendedFromId: (row as ContractRow & { amendedFromId?: string | null }).amendedFromId ?? null,
    effectiveDate: (row as ContractRow & { effectiveDate?: string | null }).effectiveDate ?? null,
    expiryDate: (row as ContractRow & { expiryDate?: string | null }).expiryDate ?? null,
    signerEmail: row.signerEmail ?? null,
    signerName: row.signerName ?? null,
    metadata: row.metadata ?? {},
    createdAt: row.createdAt.toISOString(),
    updatedAt: row.updatedAt.toISOString(),
  }
}

function serializeLineageItem(row: ContractRow): ContractLineageItem {
  return {
    id: row.id,
    title: row.title,
    status: row.status as ContractLineageItem['status'],
    renewedFromId: (row as ContractRow & { renewedFromId?: string | null }).renewedFromId ?? null,
    amendedFromId: (row as ContractRow & { amendedFromId?: string | null }).amendedFromId ?? null,
    effectiveDate: (row as ContractRow & { effectiveDate?: string | null }).effectiveDate ?? null,
    expiryDate: (row as ContractRow & { expiryDate?: string | null }).expiryDate ?? null,
    createdAt: row.createdAt.toISOString(),
  }
}

// ── renewContract ─────────────────────────────────────────────────────────────

export interface RenewContractInput {
  title: string
  effectiveDate?: string | null
  expiryDate?: string | null
}

export async function renewContract(
  db: Db,
  tenantId: string,
  contractId: string,
  userId: string,
  data: RenewContractInput,
): Promise<RenewalContract> {
  return db.transaction(async (tx) => {
    // Fetch and validate the original contract
    const existing = await tx
      .select()
      .from(contracts)
      .where(and(eq(contracts.tenantId, tenantId), eq(contracts.id, contractId)))
      .limit(1)

    if (existing.length === 0) throw new Error(`Contract not found: ${contractId}`)
    const origin = existing[0]!
    if (origin.status !== 'signed') {
      throw new Error(`Cannot renew contract in status: ${origin.status}`)
    }

    const now = new Date()

    // Create new DRAFT renewal contract
    const [newContract] = await tx
      .insert(contracts)
      .values({
        tenantId,
        customerId: origin.customerId ?? null,
        title: data.title,
        content: origin.content,
        status: 'draft',
        signerEmail: origin.signerEmail ?? null,
        signerName: origin.signerName ?? null,
        metadata: (origin.metadata ?? {}) as Record<string, unknown>,
        renewedFromId: contractId,
        effectiveDate: data.effectiveDate ?? null,
        expiryDate: data.expiryDate ?? null,
        createdAt: now,
        updatedAt: now,
      })
      .returning()

    if (!newContract) throw new Error('Insert failed')

    // Clone signatories with fresh tokens (NEVER reuse the original token)
    const originalSignatories = await tx
      .select()
      .from(contractSignatories)
      .where(eq(contractSignatories.contractId, contractId))

    for (const sig of originalSignatories) {
      const freshToken = await generateSignatoryToken()
      await tx.insert(contractSignatories).values({
        contractId: newContract.id,
        tenantId,
        email: sig.email,
        name: sig.name ?? null,
        token: freshToken,
        signingOrder: sig.signingOrder ?? null,
        createdAt: now,
      })
    }

    // Audit log — action 'contract.created' per architecture
    await tx.insert(auditLog).values({
      tenantId,
      actorId: userId,
      actorType: 'user',
      entityType: 'contract',
      entityId: newContract.id,
      action: 'contract.created',
      changes: { renewedFromId: [null, contractId] },
    })

    return serializeRow(newContract)
  })
}

// ── amendContract ─────────────────────────────────────────────────────────────

export interface AmendContractInput {
  title: string
  content: string
  effectiveDate?: string | null
}

export async function amendContract(
  db: Db,
  tenantId: string,
  contractId: string,
  userId: string,
  data: AmendContractInput,
): Promise<RenewalContract> {
  return db.transaction(async (tx) => {
    // Fetch and validate the original contract
    const existing = await tx
      .select()
      .from(contracts)
      .where(and(eq(contracts.tenantId, tenantId), eq(contracts.id, contractId)))
      .limit(1)

    if (existing.length === 0) throw new Error(`Contract not found: ${contractId}`)
    const origin = existing[0]!
    if (origin.status !== 'signed') {
      throw new Error(`Cannot amend contract in status: ${origin.status}`)
    }

    const now = new Date()

    // Create new DRAFT amendment contract
    const [newContract] = await tx
      .insert(contracts)
      .values({
        tenantId,
        customerId: origin.customerId ?? null,
        title: data.title,
        content: data.content,
        status: 'draft',
        signerEmail: origin.signerEmail ?? null,
        signerName: origin.signerName ?? null,
        metadata: (origin.metadata ?? {}) as Record<string, unknown>,
        amendedFromId: contractId,
        effectiveDate: data.effectiveDate ?? null,
        createdAt: now,
        updatedAt: now,
      })
      .returning()

    if (!newContract) throw new Error('Insert failed')

    // Clone signatories with fresh tokens (NEVER reuse the original token)
    const originalSignatories = await tx
      .select()
      .from(contractSignatories)
      .where(eq(contractSignatories.contractId, contractId))

    for (const sig of originalSignatories) {
      const freshToken = await generateSignatoryToken()
      await tx.insert(contractSignatories).values({
        contractId: newContract.id,
        tenantId,
        email: sig.email,
        name: sig.name ?? null,
        token: freshToken,
        signingOrder: sig.signingOrder ?? null,
        createdAt: now,
      })
    }

    // Audit log
    await tx.insert(auditLog).values({
      tenantId,
      actorId: userId,
      actorType: 'user',
      entityType: 'contract',
      entityId: newContract.id,
      action: 'contract.created',
      changes: { amendedFromId: [null, contractId] },
    })

    return serializeRow(newContract)
  })
}

// ── getContractLineage ────────────────────────────────────────────────────────

export async function getContractLineage(
  db: Db,
  tenantId: string,
  contractId: string,
): Promise<ContractLineage> {
  // Fetch the target contract
  const current = await db
    .select()
    .from(contracts)
    .where(and(eq(contracts.tenantId, tenantId), eq(contracts.id, contractId)))
    .limit(1)

  if (current.length === 0) throw new Error(`Contract not found: ${contractId}`)
  const currentRow = current[0]!

  // Fetch renewals (contracts that renewed FROM this one)
  const renewals = await db
    .select()
    .from(contracts)
    .where(
      and(
        eq(contracts.tenantId, tenantId),
        sql`${(contracts as unknown as Record<string, unknown>)['renewedFromId']} = ${contractId}`,
      ),
    )
    .orderBy(desc(contracts.createdAt))
    .limit(50)

  // Fetch amendments (contracts that amended FROM this one)
  const amendments = await db
    .select()
    .from(contracts)
    .where(
      and(
        eq(contracts.tenantId, tenantId),
        sql`${(contracts as unknown as Record<string, unknown>)['amendedFromId']} = ${contractId}`,
      ),
    )
    .orderBy(desc(contracts.createdAt))
    .limit(50)

  // Walk ancestors (up to 10 levels)
  const ancestors: ContractRow[] = []
  let cursor: ContractRow = currentRow
  for (let i = 0; i < 10; i++) {
    const parentId =
      (cursor as ContractRow & { renewedFromId?: string | null }).renewedFromId ??
      (cursor as ContractRow & { amendedFromId?: string | null }).amendedFromId
    if (!parentId) break
    const parent = await db
      .select()
      .from(contracts)
      .where(and(eq(contracts.tenantId, tenantId), eq(contracts.id, parentId)))
      .limit(1)
    if (parent.length === 0) break
    ancestors.unshift(parent[0]!)
    cursor = parent[0]!
  }

  return {
    current: serializeLineageItem(currentRow),
    ancestors: ancestors.map(serializeLineageItem),
    renewals: renewals.map(serializeLineageItem),
    amendments: amendments.map(serializeLineageItem),
  }
}

// ── getExpiringContracts ──────────────────────────────────────────────────────

/**
 * Returns SIGNED contracts whose expiry_date is within [today, today + daysAhead].
 * Used by the contract-expiry-reminder cron.
 * Only returns contracts that have NOT already been renewed (no renewal child exists).
 *
 * Uses raw SQL to avoid complex Drizzle type-casting for the new DATE columns.
 */
export async function getExpiringContracts(
  db: Db,
  daysAhead: number,
): Promise<ExpiringContract[]> {
  const today = new Date()
  today.setHours(0, 0, 0, 0)
  const cutoff = new Date(today)
  cutoff.setDate(cutoff.getDate() + daysAhead)

  const todayStr = today.toISOString().slice(0, 10)
  const cutoffStr = cutoff.toISOString().slice(0, 10)

  // Raw SQL: signed + expiry in window + not yet renewed (no child with renewed_from_id)
  const result = await db.execute(
    sql`SELECT id, tenant_id, title, expiry_date, signer_email
        FROM contracts
        WHERE status = 'signed'
          AND expiry_date IS NOT NULL
          AND expiry_date >= ${todayStr}::date
          AND expiry_date <= ${cutoffStr}::date
          AND id NOT IN (
            SELECT renewed_from_id FROM contracts WHERE renewed_from_id IS NOT NULL
          )
        ORDER BY expiry_date ASC
        LIMIT 500`,
  )

  type Row = { id: string; tenant_id: string; title: string; expiry_date: string; signer_email: string | null }
  const rows = result as unknown as Row[]

  return rows.map((r) => ({
    id: r.id,
    tenantId: r.tenant_id,
    title: r.title,
    expiryDate: r.expiry_date,
    signerEmail: r.signer_email ?? null,
  }))
}
