import { FACT_STATUSES } from '@pig/core';
import type { Fact, Database } from '@pig/db';
import { accounts, contacts, facts } from '@pig/db';
import { and, desc, eq } from 'drizzle-orm';
import { Hono } from 'hono';
import { z } from 'zod';
import {
  apiError,
  MutationError,
  mutation,
  type ApiEnv,
  type MutationDefinition,
} from '../lib/mutation';

const factDecisionSchema = z
  .object({
    status: z.enum(FACT_STATUSES).refine(
      (status): status is 'approved' | 'dismissed' =>
        status === 'approved' || status === 'dismissed',
      'A review may only approve or dismiss a proposed fact.',
    ),
  })
  .strict();

export interface FactDecisionResult {
  fact: Fact;
  /** Approval records a judgement; applying arbitrary field names is a separate safe system. */
  recordUpdated: false;
}

function carriesEvidence(fact: Fact): boolean {
  return Boolean(
    fact.sourceUrl?.trim() ||
      (fact.evidence && Object.keys(fact.evidence).length > 0),
  );
}

/**
 * Human approval accepts the evidence, not an untyped write to an arbitrary
 * CRM field. Keeping those operations separate prevents a plausible claim
 * such as `email` from silently overwriting a real person's record.
 */
export const factDecisionDefinition: MutationDefinition<
  typeof factDecisionSchema,
  FactDecisionResult
> = {
  schema: factDecisionSchema,
  // `fact:review`, not `data:import`. Accepting an agent's claim about a named
  // person is a judgement about evidence; rewriting five thousand rows from a
  // spreadsheet is not. They shared a capability until an audit noticed that
  // granting either granted both.
  permission: { capability: 'fact:review', team: 'research' },
  invalidMessage: 'Invalid fact review decision.',
  async mutate({ input, params, principal, tx, now }) {
    const id = params.id;
    if (!id) throw MutationError.notFound('Fact');

    const [before] = await tx.select().from(facts).where(eq(facts.id, id)).limit(1);
    if (!before) throw MutationError.notFound('Fact');
    if (before.status !== 'proposed') {
      throw new MutationError(
        'fact_already_decided',
        'Only a proposed fact can be reviewed.',
        409,
      );
    }
    if (input.status === 'approved' && !carriesEvidence(before)) {
      throw new MutationError(
        'missing_evidence',
        'A fact needs a source or evidence before it can be approved.',
        409,
      );
    }

    const [updated] = await tx
      .update(facts)
      .set({
        status: input.status,
        decidedByUserId: principal.userId,
        decidedAt: now,
      })
      .where(and(eq(facts.id, id), eq(facts.status, 'proposed')))
      .returning();
    if (!updated) {
      throw new MutationError(
        'fact_already_decided',
        'This fact was reviewed by someone else.',
        409,
      );
    }

    const approved = input.status === 'approved';
    return {
      data: { fact: updated, recordUpdated: false },
      activity: {
        type: 'agent_action',
        subject: `${approved ? 'Approved' : 'Dismissed'} proposed ${before.field}`,
        body: approved
          ? 'Evidence approved; the CRM record remains unchanged until field-aware application is available.'
          : 'Proposal dismissed; the CRM record was not changed.',
        accountId: before.accountId ?? undefined,
        contactId: before.contactId ?? undefined,
        meta: {
          factId: before.id,
          decision: input.status,
          field: before.field,
          recordUpdated: false,
        },
      },
    };
  },
};

export function createFactsRoute(db: Database) {
  const route = new Hono<ApiEnv>();

  route.get('/api/facts', async (c) => {
    const parsedStatus = z
      .enum(FACT_STATUSES)
      .safeParse(c.req.query('status') ?? 'proposed');
    if (!parsedStatus.success) {
      return c.json(
        apiError('invalid_request', 'Unknown fact status.', parsedStatus.error.issues),
        400,
      );
    }

    const rows = await db
      .select({
        fact: facts,
        accountName: accounts.name,
        contactName: contacts.fullName,
      })
      .from(facts)
      .leftJoin(accounts, eq(facts.accountId, accounts.id))
      .leftJoin(contacts, eq(facts.contactId, contacts.id))
      .where(eq(facts.status, parsedStatus.data))
      .orderBy(desc(facts.observedAt))
      .limit(200);

    return c.json({ facts: rows });
  });

  route.patch('/api/facts/:id/decision', mutation(db, factDecisionDefinition));

  return route;
}
