/**
 * POST /api/analyzer/itglue/applications/:id/apply
 *
 * Body: ApplyAssetSuggestionRequest = { auditId, fieldName, suggestedValue, sourceEvidence? }
 *
 * Flow:
 *   1. Verify auth + itglue.write permission.
 *   2. Read the asset's current traits (the source of truth for before_value).
 *   3. Insert pending row in itglue_writes capturing before/after.
 *   4. Call IT Glue PATCH /flexible_assets/:id (merged trait map).
 *   5. On success: mark write committed, refresh the local mirror row, write
 *      a generic audit_log entry.
 *   6. On failure: mark write failed, return 502.
 */

import { NextRequest, NextResponse } from 'next/server';
import { requirePermission } from '@/lib/auth-utils';
import postgresClient from '@/lib/services/postgres-client';
import { ApplyAssetSuggestionRequest } from '@/lib/types/analyzer';
import {
  createPendingWrite,
  fieldNameToTraitKey,
  getAssetAuditById,
  markWriteCommitted,
  markWriteFailed,
} from '@/lib/services/analyzer/asset-audit/persistence';
import { insertUpdatedXref } from '@/lib/services/analyzer/asset-audit/xrefs';
import { getITGlueClient } from '@/lib/services/itglue-client';
import { getITGlueSyncService } from '@/lib/services/itglue-sync-service';
import { audit } from '@/lib/services/audit';

interface AssetRow {
  id: string;
  organization_name: string | null;
  flexible_asset_type_id: string;
  traits: Record<string, unknown>;
}

async function loadAssetRow(assetId: string): Promise<AssetRow | null> {
  const res = await postgresClient.query<AssetRow>(
    `SELECT id::text                       AS id,
            organization_name,
            flexible_asset_type_id::text   AS flexible_asset_type_id,
            traits
       FROM itg_flexible_assets
      WHERE id = $1
      LIMIT 1`,
    [assetId]
  );
  return res.rowCount === 0 ? null : res.rows[0];
}

export async function POST(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { session, error } = await requirePermission('itglue', 'write');
  if (error) return error;

  const { id: assetId } = await params;
  const body = await request.json().catch(() => ({}));
  const parsed = ApplyAssetSuggestionRequest.safeParse(body);
  if (!parsed.success) {
    return NextResponse.json(
      { error: 'Invalid request body', details: parsed.error.issues },
      { status: 400 }
    );
  }
  const { auditId, fieldName, suggestedValue, sourceEvidence } = parsed.data;

  // Sanity-check: the audit must exist and reference this asset.
  const auditRow = await getAssetAuditById(auditId);
  if (!auditRow) {
    return NextResponse.json({ error: 'Audit not found' }, { status: 404 });
  }
  if (auditRow.asset_id !== assetId) {
    return NextResponse.json(
      { error: 'Audit does not reference this asset' },
      { status: 400 }
    );
  }

  // Refuse to write to credential-shaped fields. Belt for the prompt's
  // braces; the prompt already tells the LLM not to suggest these, but
  // re-block here so a malicious-looking payload can't sneak through.
  const lowerField = fieldName.toLowerCase();
  if (
    /(password|secret|key|token|credential)/.test(lowerField)
  ) {
    return NextResponse.json(
      { error: 'Refusing to write to credential-shaped field' },
      { status: 400 }
    );
  }

  // Read current traits.
  const asset = await loadAssetRow(assetId);
  if (!asset) {
    return NextResponse.json({ error: 'Asset not found' }, { status: 404 });
  }
  const traitKey = fieldNameToTraitKey(fieldName);
  const beforeValue = asset.traits[traitKey] ?? null;
  const userId =
    (session?.user as { id: string; email?: string } | undefined)?.id ?? null;
  const userEmail =
    (session?.user as { id: string; email?: string } | undefined)?.email ??
    undefined;

  // Insert pending row first so we never write to IT Glue without an audit
  // row in flight.
  const writeRow = await createPendingWrite({
    audit_id: auditId,
    asset_type: 'flexible_asset',
    asset_id: assetId,
    field_name: fieldName,
    before_value: beforeValue,
    after_value: suggestedValue,
    performed_by_user_id: userId,
    source_evidence: sourceEvidence ?? null,
    triggered_by_ticket_number: auditRow.triggered_by_ticket_number ?? null,
  });

  // Build merged trait map. IT Glue replaces the trait set on PATCH, so we
  // must include unchanged traits.
  const merged: Record<string, unknown> = {
    ...asset.traits,
    [traitKey]: suggestedValue,
  };

  try {
    const client = getITGlueClient();
    const updated = await client.updateFlexibleAsset(assetId, merged);
    await markWriteCommitted(writeRow.id, updated);

    // Best-effort: refresh the mirror so subsequent reads see the new value
    // without waiting for the next fullSync.
    try {
      await getITGlueSyncService().refreshFlexibleAssetById(assetId);
    } catch (refreshErr) {
      console.warn(
        `[itglue-apply] mirror refresh failed for asset ${assetId}:`,
        refreshErr instanceof Error ? refreshErr.message : refreshErr
      );
    }

    // Generic admin-visible audit log row.
    await audit.log({
      userId: userId ?? undefined,
      userEmail,
      action: 'itglue.write',
      resource: 'flexible_asset',
      resourceId: assetId,
      details: {
        write_id: writeRow.id,
        audit_id: auditId,
        field_name: fieldName,
        trait_key: traitKey,
        before: beforeValue,
        after: suggestedValue,
      },
    });

    // Phase 4.1: cross-reference row when this write was triggered by a
    // ticket-scoped audit. Best-effort.
    if (auditRow.triggered_by_ticket_number) {
      try {
        await insertUpdatedXref({
          ticketNumber: auditRow.triggered_by_ticket_number,
          analysisId: auditRow.triggered_by_analysis_id ?? null,
          assetType: 'flexible_asset',
          assetId,
          writeId: writeRow.id,
          fieldName,
        });
      } catch (xrefErr) {
        console.warn(
          `[itglue-apply] xref insert failed for write ${writeRow.id}:`,
          xrefErr instanceof Error ? xrefErr.message : xrefErr
        );
      }
    }

    return NextResponse.json({
      writeId: writeRow.id,
      status: 'committed',
      asset: updated,
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    await markWriteFailed(writeRow.id, message);
    return NextResponse.json(
      {
        writeId: writeRow.id,
        status: 'failed',
        error: 'IT Glue write failed',
        message,
      },
      { status: 502 }
    );
  }
}
