/**
 * Mock LlmProvider (id 'mock', model 'TEST') — deterministic, network-free, zero-cost.
 * DEV/testing only. Default = approve/success. Programmable per-job via:
 *   - input sentinel @@MOCK:DIRECTIVE[:arg]@@ embedded in prompt content
 *   - model-string directive: TEST | TEST-RATELIMIT | TEST-TRANSIENT | TEST-QUOTA | TEST-AUTH | TEST-ERROR
 * Precedence: input sentinel > model-string directive > default success.
 *
 * See docs/specs/2026-06-08-mock-llm-adapter-design.md.
 */

import type { AiCredentials } from '@/server/ai/credentials';
import { captureCaught } from '@/server/observability/capture.server';
import type {
  ImageRequest,
  LlmProvider,
  MockResponseKind,
  NormalizedTextResult,
  TextRequest,
} from './types';
import {
  ProviderAuthError,
  ProviderFatalError,
  ProviderQuotaExhaustedError,
  ProviderRateLimitError,
  ProviderTransientError,
} from './types';

const SENTINEL_RE = /@@MOCK:([A-Z_]+)(?::([^@]*))?@@/;

interface Directive {
  kind: string; // APPROVE|FLAG|REJECT|ERROR|RATELIMIT|TRANSIENT|QUOTA|AUTH|TRANSLATE_FAIL|ESCALATE
  arg?: string;
}

const THROW_DIRECTIVES = new Set(['ERROR', 'RATELIMIT', 'TRANSIENT', 'QUOTA', 'AUTH']);

function parseSentinel(prompt: string): Directive | null {
  const m = SENTINEL_RE.exec(prompt);
  if (!m) return null;
  return { kind: m[1]!, arg: m[2]?.trim() || undefined };
}

function stripSentinel(text: string): string {
  return text.replace(SENTINEL_RE, '').trim();
}

function modelDirective(model: string): Directive | null {
  if (!model.startsWith('TEST')) return null;
  const suffix = model.slice('TEST'.length).replace(/^-/, '');
  if (suffix === 'RATELIMIT') return { kind: 'RATELIMIT' };
  if (suffix === 'TRANSIENT') return { kind: 'TRANSIENT' };
  if (suffix === 'QUOTA') return { kind: 'QUOTA' };
  if (suffix === 'AUTH') return { kind: 'AUTH' };
  if (suffix === 'ERROR') return { kind: 'ERROR' };
  return null; // 'TEST' (or unknown suffix) -> success
}

function resolveDirective(req: { model: string; prompt: string }): Directive | null {
  return parseSentinel(req.prompt) ?? modelDirective(req.model);
}

function throwForDirective(kind: string, arg?: string): never {
  const msg = arg ?? `[TEST] mock ${kind}`;
  if (kind === 'RATELIMIT') throw new ProviderRateLimitError(msg);
  if (kind === 'TRANSIENT') throw new ProviderTransientError(msg);
  if (kind === 'QUOTA') throw new ProviderQuotaExhaustedError(msg);
  if (kind === 'AUTH') throw new ProviderAuthError(msg);
  throw new ProviderFatalError(msg); // ERROR + any unknown throw directive
}

function ok(text: string, model: string): NormalizedTextResult {
  return {
    text,
    model,
    usage: { promptTokens: 1, completionTokens: 1, totalTokens: 2 },
    costUsd: 0,
    rawResponse: { mock: true },
  };
}

// kind inference (fallback when caller omits kind)
function inferKind(prompt: string): MockResponseKind {
  if (prompt.includes('Fields to translate:')) return 'TRANSLATION';
  if (prompt.includes('"decision"') && prompt.includes('"confidence"')) return 'SUPPORT';
  return 'DEAL_MODERATION'; // DEAL_MODERATION + VENDOR_VIOLATION share DECISION-text
}

// DECISION-text (deal-moderation + vendor-violation)
function decisionText(directive: Directive | null): string {
  if (directive?.kind === 'FLAG') {
    const reason = directive.arg ?? '[TEST] flagged by mock';
    return `DECISION: FLAG\nRATIONALE: ${reason}\nREASON: ${reason}`;
  }
  if (directive?.kind === 'REJECT') {
    const reason = directive.arg ?? '[TEST] rejected by mock';
    return `DECISION: REJECT\nRATIONALE: ${reason}\nREASON: ${reason}`;
  }
  return 'DECISION: APPROVE\nRATIONALE: [TEST] approved by mock\nREASON: [TEST] approved by mock';
}

// translation JSON
function parseTranslateKeys(prompt: string): Record<string, string> {
  const idx = prompt.indexOf('Fields to translate:');
  if (idx === -1) return {};
  const after = prompt.slice(idx + 'Fields to translate:'.length);
  const start = after.indexOf('{');
  if (start === -1) return {};
  let depth = 0;
  let end = -1;
  for (let i = start; i < after.length; i++) {
    if (after[i] === '{') depth++;
    else if (after[i] === '}') {
      depth--;
      if (depth === 0) {
        end = i;
        break;
      }
    }
  }
  if (end === -1) return {};
  try {
    const parsed = JSON.parse(after.slice(start, end + 1)) as Record<string, unknown>;
    const out: Record<string, string> = {};
    for (const [k, v] of Object.entries(parsed)) out[k] = typeof v === 'string' ? v : '';
    return out;
  } catch (err) {
    captureCaught(err, { scope: 'ai.mock.parseTranslateKeys', severity: 'info' });
    return {};
  }
}

function translationText(prompt: string, directive: Directive | null): string {
  if (directive?.kind === 'TRANSLATE_FAIL') {
    return 'NOT_JSON [TEST] mock translate failure';
  }
  const keys = parseTranslateKeys(prompt);
  const obj: Record<string, string> = {};
  for (const [k, v] of Object.entries(keys)) {
    const src = stripSentinel(v);
    obj[k] = `[TEST-EN] ${src || k}`;
  }
  if (Object.keys(obj).length === 0) {
    obj.title = '[TEST-EN] title';
    obj.description = '[TEST-EN] description';
  }
  return JSON.stringify(obj);
}

// support AgentTurn JSON
function parseSupportDecisions(prompt: string): string[] {
  const out: string[] = [];
  const re = /^- ([a-z0-9_-]+):/gm;
  let m: RegExpExecArray | null;
  while ((m = re.exec(prompt)) !== null) out.push(m[1]!);
  return out;
}

function supportText(prompt: string, directive: Directive | null): string {
  const decisions = parseSupportDecisions(prompt);
  let decision: string;
  let confidence = 0.95;
  if (directive?.kind === 'ESCALATE') {
    decision = decisions.includes('escalate') ? 'escalate' : (decisions[0] ?? 'escalate');
    confidence = 0.1;
  } else if (prompt.includes('המשך לפי ההחלטה')) {
    decision = 'close';
  } else {
    const preferred = decisions.find((d) => /(close|resolve|reply|answer|ask)/.test(d));
    decision = preferred ?? decisions[0] ?? 'escalate';
  }
  return JSON.stringify({
    decision,
    confidence,
    message: '[TEST] mock support reply',
    reasoning: directive?.arg ?? '[TEST] auto-handled by mock',
  });
}

// image blobs (hint-free; all image parsers are substring-based)
const IMAGE_APPROVE_BLOB = [
  'DECISION: PASS',
  'DECISION: APPROVE',
  'DECISION: AUTO_APPROVE',
  'DECISION: APPROVED',
  'SCORE: 0.99',
  'CONFIDENCE: 0.99',
  'REASON: [TEST] auto-approved by mock',
  'REASON_CODE: NONE',
  'FLAGS: none',
].join('\n');

function imageRejectBlob(reason: string): string {
  return [
    'DECISION: REJECT',
    'DECISION: REJECTED',
    'SCORE: 0.99',
    'CONFIDENCE: 0.99',
    `REASON: ${reason}`,
    'REASON_CODE: POLICY_VIOLATION',
    `FLAGS: ${reason}`,
  ].join('\n');
}

// LlmProvider impl
async function generateText(
  _creds: AiCredentials,
  req: TextRequest,
): Promise<NormalizedTextResult> {
  const directive = resolveDirective(req);
  if (directive && THROW_DIRECTIVES.has(directive.kind)) {
    throwForDirective(directive.kind, directive.arg);
  }
  const kind = req.kind ?? inferKind(req.prompt);
  if (kind === 'TRANSLATION') return ok(translationText(req.prompt, directive), req.model);
  if (kind === 'SUPPORT') return ok(supportText(req.prompt, directive), req.model);
  return ok(decisionText(directive), req.model);
}

async function generateTextWithImage(
  _creds: AiCredentials,
  req: ImageRequest,
): Promise<NormalizedTextResult> {
  const directive = resolveDirective(req);
  if (directive && THROW_DIRECTIVES.has(directive.kind)) {
    throwForDirective(directive.kind, directive.arg);
  }
  if (directive?.kind === 'FLAG' || directive?.kind === 'REJECT') {
    return ok(imageRejectBlob(directive.arg ?? '[TEST] mock reject'), req.model);
  }
  return ok(IMAGE_APPROVE_BLOB, req.model);
}

export const mockProvider: LlmProvider = {
  id: 'mock',
  supportsImage: true,
  generateText,
  generateTextWithImage,
};
