/**
 * Contract signing query helpers — contract-signing-page (wave-11 leaf C).
 *
 * Public (unauthenticated) path: resolve by signatory token.
 * No auth required — token is the credential.
 */
import { and, desc, eq } from 'drizzle-orm'
import type { Db } from '../client'
import { contracts } from '../schema/contracts'
import { contractSignatories } from '../schema/contracts'
import { contractAuditLog } from '../schema/contracts'

// ── Types ─────────────────────────────────────────────────────────────────────

export interface SignatoryWithContract {
  signatoryId: string
  contractId: string
  tenantId: string
  email: string
  name: string | null
  order: number
  tokenExpiresAt: string | null
  signedAt: string | null
  declinedAt: string | null
  declinedReason: string | null
  contract: {
    title: string
    content: string
    status: string
    signingDeadline: string | null
    signedPdfR2Key: string | null
    customerId: string | null
  }
}

export interface SignContractInput {
  signatoryId: string
  signatureData: unknown
  signatureType: 'drawn' | 'typed'
  signerIp: string | null
  signerUserAgent: string | null
  fullName: string
  email: string
}

// ── Queries ───────────────────────────────────────────────────────────────────

export async function getSignatoryByToken(
  db: Db,
  token: string,
): Promise<SignatoryWithContract | null> {
  const rows = await db
    .select({
      signatoryId: contractSignatories.id,
      contractId: contractSignatories.contractId,
      tenantId: contractSignatories.tenantId,
      email: contractSignatories.email,
      name: contractSignatories.name,
      order: contractSignatories.order,
      tokenExpiresAt: contractSignatories.tokenExpiresAt,
      signedAt: contractSignatories.signedAt,
      declinedAt: contractSignatories.declinedAt,
      contractTitle: contracts.title,
      contractContent: contracts.content,
      contractStatus: contracts.status,
      contractSigningDeadline: contracts.signingDeadline,
      contractSignedPdfR2Key: contracts.signedPdfR2Key,
      contractCustomerId: contracts.customerId,
    })
    .from(contractSignatories)
    .innerJoin(contracts, eq(contractSignatories.contractId, contracts.id))
    .where(eq(contractSignatories.token, token))
    .limit(1)

  if (rows.length === 0) return null
  const row = rows[0]!
  const [declineAudit] = row.declinedAt
    ? await db
        .select({ metadata: contractAuditLog.metadata })
        .from(contractAuditLog)
        .where(
          and(
            eq(contractAuditLog.contractId, row.contractId),
            eq(contractAuditLog.event, 'declined'),
            eq(contractAuditLog.actorId, row.email),
          ),
        )
        .orderBy(desc(contractAuditLog.createdAt))
        .limit(1)
    : []
  const declinedReason =
    declineAudit?.metadata &&
    typeof declineAudit.metadata === 'object' &&
    'reason' in declineAudit.metadata &&
    typeof declineAudit.metadata.reason === 'string'
      ? declineAudit.metadata.reason
      : null

  return {
    signatoryId: row.signatoryId,
    contractId: row.contractId,
    tenantId: row.tenantId,
    email: row.email,
    name: row.name ?? null,
    order: row.order,
    tokenExpiresAt: row.tokenExpiresAt ? row.tokenExpiresAt.toISOString() : null,
    signedAt: row.signedAt ? row.signedAt.toISOString() : null,
    declinedAt: row.declinedAt ? row.declinedAt.toISOString() : null,
    declinedReason,
    contract: {
      title: row.contractTitle,
      content: row.contractContent,
      status: row.contractStatus,
      signingDeadline: row.contractSigningDeadline
        ? row.contractSigningDeadline.toISOString()
        : null,
      signedPdfR2Key: row.contractSignedPdfR2Key ?? null,
      customerId: row.contractCustomerId ?? null,
    },
  }
}

export async function signContract(
  db: Db,
  input: SignContractInput,
): Promise<{ ok: boolean; alreadySigned: boolean }> {
  const current = await db
    .select({
      signedAt: contractSignatories.signedAt,
      contractId: contractSignatories.contractId,
      tenantId: contractSignatories.tenantId,
    })
    .from(contractSignatories)
    .where(eq(contractSignatories.id, input.signatoryId))
    .limit(1)

  if (current.length === 0) return { ok: false, alreadySigned: false }
  if (current[0]!.signedAt !== null) return { ok: true, alreadySigned: true }

  const signatoryRow = current[0]!
  const contractId = signatoryRow.contractId
  const tenantId = signatoryRow.tenantId

  await db.transaction(async (tx) => {
    await tx
      .update(contractSignatories)
      .set({
        signedAt: new Date(),
        signatureData: input.signatureData,
        signatureType: input.signatureType,
        signerIp: input.signerIp,
        signerUserAgent: input.signerUserAgent,
        name: input.fullName,
        email: input.email,
      })
      .where(
        and(
          eq(contractSignatories.tenantId, tenantId),
          eq(contractSignatories.id, input.signatoryId),
        ),
      )

    await tx.insert(contractAuditLog).values({
      contractId,
      tenantId,
      event: 'signed',
      actorType: 'signatory',
      actorId: input.email,
      ipAddress: input.signerIp,
      userAgent: input.signerUserAgent,
    })

    const allSignatories = await tx
      .select({ signedAt: contractSignatories.signedAt })
      .from(contractSignatories)
      .where(eq(contractSignatories.contractId, contractId))

    const allSigned = allSignatories.length > 0 && allSignatories.every((s) => s.signedAt !== null)
    if (allSigned) {
      await tx
        .update(contracts)
        .set({ status: 'signed', signedAt: new Date(), updatedAt: new Date() })
        .where(and(eq(contracts.tenantId, tenantId), eq(contracts.id, contractId)))
    }
  })

  return { ok: true, alreadySigned: false }
}

export async function declineContract(
  db: Db,
  signatoryId: string,
  contractId: string,
  tenantId: string,
  actorEmail: string,
  reason: string | null,
  ip: string | null,
  userAgent: string | null,
): Promise<{ ok: boolean; alreadyDone: boolean }> {
  const current = await db
    .select({ declinedAt: contractSignatories.declinedAt, signedAt: contractSignatories.signedAt })
    .from(contractSignatories)
    .where(eq(contractSignatories.id, signatoryId))
    .limit(1)

  if (current.length === 0) return { ok: false, alreadyDone: false }
  if (current[0]!.declinedAt !== null || current[0]!.signedAt !== null) {
    return { ok: true, alreadyDone: true }
  }

  await db.transaction(async (tx) => {
    await tx
      .update(contractSignatories)
      .set({ declinedAt: new Date() })
      .where(and(eq(contractSignatories.tenantId, tenantId), eq(contractSignatories.id, signatoryId)))

    await tx.insert(contractAuditLog).values({
      contractId,
      tenantId,
      event: 'declined',
      actorType: 'signatory',
      actorId: actorEmail,
      ipAddress: ip,
      userAgent,
      metadata: reason ? { reason } : {},
    })
  })

  return { ok: true, alreadyDone: false }
}

export async function recordContractView(
  db: Db,
  contractId: string,
  tenantId: string,
  actorEmail: string,
  ip: string | null,
  userAgent: string | null,
): Promise<void> {
  await db.insert(contractAuditLog).values({
    contractId,
    tenantId,
    event: 'viewed',
    actorType: 'signatory',
    actorId: actorEmail,
    ipAddress: ip,
    userAgent,
  })
}

export async function recordContractAuditEvent(
  db: Db,
  input: {
    contractId: string
    tenantId: string
    event: 'sent' | 'viewed' | 'signed' | 'declined' | 'voided' | 'downloaded' | 'reminder_sent'
    actorType: 'staff' | 'signatory'
    actorId: string
    ipAddress: string | null
    userAgent: string | null
    metadata?: Record<string, unknown>
  },
): Promise<void> {
  await db.insert(contractAuditLog).values({
    contractId: input.contractId,
    tenantId: input.tenantId,
    event: input.event,
    actorType: input.actorType,
    actorId: input.actorId,
    ipAddress: input.ipAddress,
    userAgent: input.userAgent,
    metadata: input.metadata ?? {},
  })
}
