/**
 * Persistence helpers for `itglue_asset_audits` and `itglue_writes`.
 *
 * Audit rows accumulate full LLM context snapshots (forever-retained).
 * Write rows record every IT Glue PATCH attempt with before/after diffs
 * and provenance back to the audit that prompted the change.
 */

import postgresClient from '@/lib/services/postgres-client';
import type {
  AssetAuditResponse,
  AuditConfidence,
} from '@/lib/types/analyzer';

// ─── Audit rows ───────────────────────────────────────────────────────────

export type AuditStatus = 'pending' | 'running' | 'complete' | 'failed';
export type WriteStatus = 'pending' | 'committed' | 'failed' | 'reverted';

export interface AssetAuditRow {
  id: string;
  asset_type: 'flexible_asset' | 'configuration';
  asset_id: string;
  asset_type_id: string | null;
  organization_id: string | null;
  generated_by_user_id: string | null;
  generated_at: string;
  provider: 'anthropic' | 'openrouter';
  model_used: string | null;
  asset_snapshot: Record<string, unknown>;
  ticket_count: number;
  field_gaps: AssetAuditResponse['field_gaps'];
  notes_promotions: AssetAuditResponse['notes_promotions'];
  contradictions: AssetAuditResponse['contradictions'];
  overall_score: number | null;
  estimated_cost_usd: number | null;
  total_input_tokens: number | null;
  total_output_tokens: number | null;
  status: AuditStatus;
  error_message: string | null;
  triggered_by_ticket_number: string | null;
  triggered_by_analysis_id: string | null;
}

const AUDIT_SELECT = `
  id::text                    AS id,
  asset_type, asset_id::text  AS asset_id,
  asset_type_id::text         AS asset_type_id,
  organization_id::text       AS organization_id,
  generated_by_user_id, generated_at,
  provider, model_used,
  asset_snapshot,
  ticket_count,
  field_gaps, notes_promotions, contradictions,
  overall_score::float8       AS overall_score,
  estimated_cost_usd::float8  AS estimated_cost_usd,
  total_input_tokens, total_output_tokens,
  status, error_message,
  triggered_by_ticket_number,
  triggered_by_analysis_id::text  AS triggered_by_analysis_id
`;

interface RawAuditRow {
  id: string;
  asset_type: 'flexible_asset' | 'configuration';
  asset_id: string;
  asset_type_id: string | null;
  organization_id: string | null;
  generated_by_user_id: string | null;
  generated_at: Date;
  provider: 'anthropic' | 'openrouter';
  model_used: string | null;
  asset_snapshot: Record<string, unknown>;
  ticket_count: number;
  field_gaps: AssetAuditResponse['field_gaps'];
  notes_promotions: AssetAuditResponse['notes_promotions'];
  contradictions: AssetAuditResponse['contradictions'];
  overall_score: number | null;
  estimated_cost_usd: number | null;
  total_input_tokens: number | null;
  total_output_tokens: number | null;
  status: AuditStatus;
  error_message: string | null;
  triggered_by_ticket_number: string | null;
  triggered_by_analysis_id: string | null;
}

function rowToAudit(r: RawAuditRow): AssetAuditRow {
  return {
    ...r,
    generated_at: r.generated_at.toISOString(),
  };
}

export interface CreateAuditInput {
  asset_type: 'flexible_asset' | 'configuration';
  asset_id: number | string;
  asset_type_id: number | null;
  organization_id: number | string | null;
  generated_by_user_id: string | null;
  provider: 'anthropic' | 'openrouter';
  model_used: string;
  asset_snapshot: Record<string, unknown>;
  ticket_count: number;
  response: AssetAuditResponse;
  estimated_cost_usd: number;
  total_input_tokens: number;
  total_output_tokens: number;
  /** Phase 4.1: ticket-first audit linkage. Both null for asset-first audits. */
  triggered_by_ticket_number?: string | null;
  triggered_by_analysis_id?: string | null;
}

export async function insertAssetAudit(input: CreateAuditInput): Promise<{ id: string }> {
  const res = await postgresClient.query<{ id: string }>(
    `INSERT INTO itglue_asset_audits
       (asset_type, asset_id, asset_type_id, organization_id,
        generated_by_user_id, provider, model_used,
        asset_snapshot, ticket_count,
        field_gaps, notes_promotions, contradictions,
        overall_score, estimated_cost_usd,
        total_input_tokens, total_output_tokens,
        status,
        triggered_by_ticket_number, triggered_by_analysis_id)
     VALUES ($1, $2, $3, $4,
             $5, $6, $7,
             $8::jsonb, $9,
             $10::jsonb, $11::jsonb, $12::jsonb,
             $13, $14,
             $15, $16,
             'complete',
             $17, $18)
     RETURNING id::text AS id`,
    [
      input.asset_type,
      input.asset_id,
      input.asset_type_id,
      input.organization_id,
      input.generated_by_user_id,
      input.provider,
      input.model_used,
      JSON.stringify(input.asset_snapshot),
      input.ticket_count,
      JSON.stringify(input.response.field_gaps),
      JSON.stringify(input.response.notes_promotions),
      JSON.stringify(input.response.contradictions),
      input.response.overall_score,
      input.estimated_cost_usd,
      input.total_input_tokens,
      input.total_output_tokens,
      input.triggered_by_ticket_number ?? null,
      input.triggered_by_analysis_id ?? null,
    ]
  );
  return { id: res.rows[0].id };
}

export async function insertFailedAssetAudit(input: {
  asset_type: 'flexible_asset' | 'configuration';
  asset_id: number | string;
  asset_type_id: number | null;
  organization_id: number | string | null;
  generated_by_user_id: string | null;
  provider: 'anthropic' | 'openrouter';
  model_used: string | null;
  asset_snapshot: Record<string, unknown>;
  ticket_count: number;
  error_message: string;
  triggered_by_ticket_number?: string | null;
  triggered_by_analysis_id?: string | null;
}): Promise<{ id: string }> {
  const res = await postgresClient.query<{ id: string }>(
    `INSERT INTO itglue_asset_audits
       (asset_type, asset_id, asset_type_id, organization_id,
        generated_by_user_id, provider, model_used,
        asset_snapshot, ticket_count,
        field_gaps, notes_promotions, contradictions,
        status, error_message,
        triggered_by_ticket_number, triggered_by_analysis_id)
     VALUES ($1, $2, $3, $4,
             $5, $6, $7,
             $8::jsonb, $9,
             '[]'::jsonb, '[]'::jsonb, '[]'::jsonb,
             'failed', $10,
             $11, $12)
     RETURNING id::text AS id`,
    [
      input.asset_type,
      input.asset_id,
      input.asset_type_id,
      input.organization_id,
      input.generated_by_user_id,
      input.provider,
      input.model_used,
      JSON.stringify(input.asset_snapshot),
      input.ticket_count,
      input.error_message,
      input.triggered_by_ticket_number ?? null,
      input.triggered_by_analysis_id ?? null,
    ]
  );
  return { id: res.rows[0].id };
}

export async function getLatestAssetAudit(
  assetId: string | number,
  assetType: 'flexible_asset' | 'configuration' = 'flexible_asset'
): Promise<AssetAuditRow | null> {
  const res = await postgresClient.query<RawAuditRow>(
    `SELECT ${AUDIT_SELECT}
       FROM itglue_asset_audits
      WHERE asset_type = $1
        AND asset_id = $2
      ORDER BY generated_at DESC
      LIMIT 1`,
    [assetType, assetId]
  );
  if (res.rowCount === 0) return null;
  return rowToAudit(res.rows[0]);
}

/**
 * Phase 4.1: fetch the most recent ticket-scoped audit for a given
 * (analysis, assetType, assetId) triple. Used by the analysis page to show
 * "we already audited this asset for this ticket — here's what came back".
 */
export async function getLatestTicketScopedAudit(
  analysisId: string,
  assetType: 'flexible_asset' | 'configuration',
  assetId: string | number
): Promise<AssetAuditRow | null> {
  const res = await postgresClient.query<RawAuditRow>(
    `SELECT ${AUDIT_SELECT}
       FROM itglue_asset_audits
      WHERE asset_type = $1
        AND asset_id = $2
        AND triggered_by_analysis_id = $3
      ORDER BY generated_at DESC
      LIMIT 1`,
    [assetType, assetId, analysisId]
  );
  if (res.rowCount === 0) return null;
  return rowToAudit(res.rows[0]);
}

export async function getAssetAuditById(id: string): Promise<AssetAuditRow | null> {
  const res = await postgresClient.query<RawAuditRow>(
    `SELECT ${AUDIT_SELECT} FROM itglue_asset_audits WHERE id = $1`,
    [id]
  );
  if (res.rowCount === 0) return null;
  return rowToAudit(res.rows[0]);
}

export async function listAssetAudits(
  assetId: string | number,
  assetType: 'flexible_asset' | 'configuration' = 'flexible_asset',
  limit = 20
): Promise<AssetAuditRow[]> {
  const res = await postgresClient.query<RawAuditRow>(
    `SELECT ${AUDIT_SELECT}
       FROM itglue_asset_audits
      WHERE asset_type = $1
        AND asset_id = $2
      ORDER BY generated_at DESC
      LIMIT $3`,
    [assetType, assetId, limit]
  );
  return res.rows.map(rowToAudit);
}

// ─── Write rows ───────────────────────────────────────────────────────────

export interface AssetWriteRow {
  id: string;
  audit_id: string | null;
  asset_type: 'flexible_asset' | 'configuration';
  asset_id: string;
  field_name: string;
  before_value: unknown;
  after_value: unknown;
  performed_by_user_id: string | null;
  performed_at: string;
  status: WriteStatus;
  itglue_response: unknown;
  error_message: string | null;
  source_evidence: unknown;
}

const WRITE_SELECT = `
  id::text                    AS id,
  audit_id::text              AS audit_id,
  asset_type, asset_id::text  AS asset_id,
  field_name,
  before_value, after_value,
  performed_by_user_id, performed_at,
  status,
  itglue_response, error_message, source_evidence
`;

interface RawWriteRow {
  id: string;
  audit_id: string | null;
  asset_type: 'flexible_asset' | 'configuration';
  asset_id: string;
  field_name: string;
  before_value: unknown;
  after_value: unknown;
  performed_by_user_id: string | null;
  performed_at: Date;
  status: WriteStatus;
  itglue_response: unknown;
  error_message: string | null;
  source_evidence: unknown;
}

function rowToWrite(r: RawWriteRow): AssetWriteRow {
  return { ...r, performed_at: r.performed_at.toISOString() };
}

export async function createPendingWrite(input: {
  audit_id: string | null;
  asset_type: 'flexible_asset' | 'configuration';
  asset_id: number | string;
  field_name: string;
  before_value: unknown;
  after_value: unknown;
  performed_by_user_id: string | null;
  source_evidence: unknown;
  /** Phase 4.1: ticket linkage carried forward from the audit row. */
  triggered_by_ticket_number?: string | null;
}): Promise<{ id: string }> {
  const res = await postgresClient.query<{ id: string }>(
    `INSERT INTO itglue_writes
       (audit_id, asset_type, asset_id, field_name,
        before_value, after_value,
        performed_by_user_id, status, source_evidence,
        triggered_by_ticket_number)
     VALUES ($1, $2, $3, $4,
             $5::jsonb, $6::jsonb,
             $7, 'pending', $8::jsonb,
             $9)
     RETURNING id::text AS id`,
    [
      input.audit_id,
      input.asset_type,
      input.asset_id,
      input.field_name,
      JSON.stringify(input.before_value ?? null),
      JSON.stringify(input.after_value),
      input.performed_by_user_id,
      JSON.stringify(input.source_evidence ?? null),
      input.triggered_by_ticket_number ?? null,
    ]
  );
  return { id: res.rows[0].id };
}

export async function markWriteCommitted(
  id: string,
  itglueResponse: unknown
): Promise<void> {
  await postgresClient.query(
    `UPDATE itglue_writes
        SET status = 'committed',
            itglue_response = $2::jsonb
      WHERE id = $1`,
    [id, JSON.stringify(itglueResponse ?? null)]
  );
}

export async function markWriteFailed(id: string, errorMessage: string): Promise<void> {
  await postgresClient.query(
    `UPDATE itglue_writes
        SET status = 'failed', error_message = $2
      WHERE id = $1`,
    [id, errorMessage]
  );
}

export async function markWriteReverted(id: string): Promise<void> {
  await postgresClient.query(
    `UPDATE itglue_writes SET status = 'reverted' WHERE id = $1`,
    [id]
  );
}

export async function getWriteById(id: string): Promise<AssetWriteRow | null> {
  const res = await postgresClient.query<RawWriteRow>(
    `SELECT ${WRITE_SELECT} FROM itglue_writes WHERE id = $1`,
    [id]
  );
  if (res.rowCount === 0) return null;
  return rowToWrite(res.rows[0]);
}

export async function listWritesForAsset(
  assetId: string | number,
  limit = 50
): Promise<AssetWriteRow[]> {
  const res = await postgresClient.query<RawWriteRow>(
    `SELECT ${WRITE_SELECT}
       FROM itglue_writes
      WHERE asset_type = 'flexible_asset'
        AND asset_id = $1
      ORDER BY performed_at DESC
      LIMIT $2`,
    [assetId, limit]
  );
  return res.rows.map(rowToWrite);
}

export async function listAllWrites(opts: {
  limit?: number;
  offset?: number;
  status?: WriteStatus;
}): Promise<AssetWriteRow[]> {
  const limit = Math.min(opts.limit ?? 100, 500);
  const offset = opts.offset ?? 0;
  const params: unknown[] = [limit, offset];
  let where = '';
  if (opts.status) {
    params.push(opts.status);
    where = `WHERE status = $${params.length}`;
  }
  const res = await postgresClient.query<RawWriteRow>(
    `SELECT ${WRITE_SELECT}
       FROM itglue_writes
       ${where}
      ORDER BY performed_at DESC
      LIMIT $1 OFFSET $2`,
    params
  );
  return res.rows.map(rowToWrite);
}

// ─── Helpers ──────────────────────────────────────────────────────────────

/**
 * The IT Glue convention: trait keys are field names lowercased, hyphenated,
 * stripped of repeated/leading/trailing hyphens. Used to map a human field
 * name (e.g. "Wulf Application Champion(s)") to its trait key.
 */
export function fieldNameToTraitKey(name: string): string {
  return name
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '');
}

export type { AssetAuditResponse, AuditConfidence };
