/**
 * Real VendorViolationFlagAgent - Gemini 2.0 Flash text implementation.
 *
 * Evaluates vendor health signals and assigns a violation severity.
 * The LLM reasons over the combination of signals holistically, which catches
 * edge cases that pure threshold logic misses (e.g. a vendor with moderate
 * scores across multiple dimensions that individually don't trip thresholds).
 *
 * On any Gemini error the agent falls back to deterministic threshold logic
 * (identical to the stub) so no vendor data is silently dropped.
 *
 * `lastUsage` is populated after each successful LLM call; callers (llm.ts)
 * read it to write token/cost data back to the llm_jobs row.
 */

import { requireConfiguredLlmModel } from '../model-config.js';
import { withSentry } from '@/server/observability/with-sentry';
import { captureCaught } from '@/server/observability/capture.server';
import type { GeminiUsage } from '../gemini.js';
import type { LlmProvider } from '../providers/types.js';
import type { AiCredentials } from '../credentials.js';
import type {
  VendorAdmissionInput,
  VendorAdmissionResult,
  VendorAdmissionReviewAgent,
  VendorViolationFlagAgent,
  VendorViolationFlagInput,
  VendorViolationFlagResult,
  VendorViolationSignals,
} from '../types.js';

export class GeminiVendorViolationFlagAgent
  implements VendorViolationFlagAgent, VendorAdmissionReviewAgent
{
  private readonly provider: LlmProvider;
  private readonly creds: AiCredentials;
  private readonly model: string;

  /** Populated after a successful LLM call; null after a fallback/error. */
  public lastUsage: GeminiUsage | null = null;

  constructor(provider: LlmProvider, creds: AiCredentials, model: string) {
    this.provider = provider;
    this.creds = creds;
    this.model = requireConfiguredLlmModel(model, 'vendor violation');
    this.flag = withSentry(this.flag.bind(this), {
      name: 'ai.vendor-violation-flag.flag',
      kind: 'ai-agent',
    });
    this.review = withSentry(this.review.bind(this), {
      name: 'ai.vendor-violation-flag.review',
      kind: 'ai-agent',
    });
  }

  flag: (input: VendorViolationFlagInput) => Promise<VendorViolationFlagResult> = async (input) => {
    const { signals } = input;

    const prompt = buildPrompt(signals);

    try {
      const {
        text: raw,
        usage,
        costUsd,
        model,
      } = await this.provider.generateText(this.creds, {
        model: this.model,
        prompt,
      });

      this.lastUsage = { ...usage, modelName: model ?? this.model, costUsd: costUsd ?? null };
      return parseResponse(raw);
    } catch (err) {
      captureCaught(err, { scope: 'server.ai.agents.vendor-violation-flag', severity: 'warning' });
      // Fail safe: fall back to deterministic threshold logic
      this.lastUsage = null;
      return deterministicFlag(signals);
    }
  };

  review: (input: VendorAdmissionInput) => Promise<VendorAdmissionResult> = async (input) => {
    const prompt = buildAdmissionPrompt(input);

    try {
      const {
        text: raw,
        usage,
        costUsd,
        model,
      } = await this.provider.generateText(this.creds, {
        model: this.model,
        prompt,
        kind: 'VENDOR_VIOLATION',
      });
      this.lastUsage = { ...usage, modelName: model ?? this.model, costUsd: costUsd ?? null };
      return parseAdmissionResponse(raw);
    } catch (err) {
      captureCaught(err, { scope: 'server.ai.agents.vendor-violation-flag', severity: 'warning' });
      // Fail safe: approve by default so new vendors aren't silently blocked
      this.lastUsage = null;
      return deterministicAdmission(input);
    }
  };
}

// ─── Prompt ──────────────────────────────────────────────────────────────────

function buildPrompt(signals: VendorViolationSignals): string {
  const dayLabel =
    signals.daysSinceFirstSale === 0
      ? 'new vendor (no sales yet)'
      : `${signals.daysSinceFirstSale} days since first sale`;

  return `You are a vendor integrity analyst for Multideal, a Hebrew marketplace where local Israeli businesses offer discounted deals to customers.

Evaluate the following vendor health signals and assign a violation severity.

Vendor signals:
- Unresolved customer complaints: ${signals.unresolvedComplaints}
- Average review rating: ${signals.avgRating.toFixed(1)} / 5.0
- Reviews removed by moderation: ${signals.removedReviews}
- Refund rate: ${(signals.refundRate * 100).toFixed(1)}%
- Vendor tenure: ${dayLabel}

Severity definitions:
- none   - Vendor appears healthy. No intervention needed.
- low    - Minor concerns worth monitoring. No immediate action required.
- medium - Meaningful risk signals. Should be reviewed by admin soon.
- high   - Serious pattern of violations or customer harm. Requires immediate admin attention.

Thresholds for reference (any single threshold can trigger, but consider the full picture):
  high:   ≥3 unresolved complaints, OR avg rating <2.0 (for established vendors), OR ≥5 removed reviews
  medium: ≥2 unresolved complaints, OR avg rating <3.0, OR ≥3 removed reviews, OR refund rate ≥20%
  low:    ≥1 unresolved complaint, OR avg rating <3.5, OR ≥1 removed review, OR refund rate ≥10%

Important: for new vendors (0 days since first sale), do NOT penalise for low ratings - they have no history.

Respond with exactly this format:
SEVERITY: none or low or medium or high
REASONS: comma-separated list of specific reasons (or "none" if severity is none)`;
}

// ─── Response parser ──────────────────────────────────────────────────────────

function parseResponse(raw: string): VendorViolationFlagResult {
  const upper = raw.toUpperCase();

  let severity: VendorViolationFlagResult['severity'] = 'none';
  if (upper.includes('SEVERITY: HIGH')) severity = 'high';
  else if (upper.includes('SEVERITY: MEDIUM')) severity = 'medium';
  else if (upper.includes('SEVERITY: LOW')) severity = 'low';

  const reasonsMatch = raw.match(/REASONS:\s*(.+)/i);
  const reasonsRaw = reasonsMatch?.[1]?.trim() ?? '';
  const reasons =
    reasonsRaw.toLowerCase() === 'none' || !reasonsRaw
      ? []
      : reasonsRaw
          .split(',')
          .map((r) => r.trim())
          .filter(Boolean);

  return { severity, reasons };
}

// ─── Deterministic fallback (mirrors stub logic) ──────────────────────────────

function deterministicFlag(signals: VendorViolationSignals): VendorViolationFlagResult {
  const reasons: string[] = [];
  let severity: VendorViolationFlagResult['severity'] = 'none';

  // High
  if (signals.unresolvedComplaints >= 3) {
    reasons.push(`${signals.unresolvedComplaints} unresolved complaints`);
    severity = 'high';
  }
  if (signals.avgRating < 2.0 && signals.daysSinceFirstSale > 0) {
    reasons.push(`average rating ${signals.avgRating.toFixed(1)} is critically low`);
    severity = 'high';
  }
  if (signals.removedReviews >= 5) {
    reasons.push(`${signals.removedReviews} reviews removed`);
    severity = 'high';
  }
  if (severity === 'high') return { severity, reasons };

  // Medium
  if (signals.unresolvedComplaints >= 2) {
    reasons.push(`${signals.unresolvedComplaints} unresolved complaints`);
    severity = 'medium';
  }
  if (signals.avgRating < 3.0 && signals.daysSinceFirstSale > 0) {
    reasons.push(`average rating ${signals.avgRating.toFixed(1)} is below acceptable threshold`);
    severity = 'medium';
  }
  if (signals.removedReviews >= 3) {
    reasons.push(`${signals.removedReviews} reviews removed`);
    severity = 'medium';
  }
  if (signals.refundRate >= 0.2) {
    reasons.push(`refund rate ${(signals.refundRate * 100).toFixed(0)}% exceeds 20%`);
    severity = 'medium';
  }
  if (severity === 'medium') return { severity, reasons };

  // Low
  if (signals.unresolvedComplaints >= 1) {
    reasons.push(`${signals.unresolvedComplaints} unresolved complaint`);
    severity = 'low';
  }
  if (signals.avgRating < 3.5 && signals.daysSinceFirstSale > 0) {
    reasons.push(`average rating ${signals.avgRating.toFixed(1)} is below 3.5`);
    severity = 'low';
  }
  if (signals.removedReviews >= 1) {
    reasons.push(`${signals.removedReviews} review(s) removed`);
    severity = 'low';
  }
  if (signals.refundRate >= 0.1) {
    reasons.push(`refund rate ${(signals.refundRate * 100).toFixed(0)}% exceeds 10%`);
    severity = 'low';
  }

  return { severity, reasons };
}

// ─── Admission review prompt + parser + fallback ──────────────────────────────

function buildAdmissionPrompt(input: VendorAdmissionInput): string {
  const daysSinceCreation = Math.floor(
    (Date.now() - input.createdAt.getTime()) / (1000 * 60 * 60 * 24),
  );

  return `You are a vendor admission reviewer for Multideal, a Hebrew-first marketplace where local Israeli businesses offer discounted deals to customers.

A new vendor has applied to join the platform. Review the registration details and decide whether to admit them.

Vendor details:
- Business name: ${input.businessName}
- Owner email: ${input.ownerEmail}
- Phone country prefix: ${input.phoneCountryPrefix}
- Account created: ${daysSinceCreation === 0 ? 'today' : `${daysSinceCreation} days ago`}

Admission criteria:
- APPROVE  - The business name looks legitimate, the email domain is plausible, and the phone country is Israel (+972) or a nearby region. No obvious spam or bot signals.
- FLAG     - Something looks suspicious but not definitively bad (unusual email domain, unclear business name, non-Israeli phone). Route to human admin for final decision.
- REJECT   - Clear indicators of abuse: all-gibberish business name, known spam/disposable email domain, or nonsensical registration data.

Important: Israeli phone prefix is +972. Neighboring regions (+961, +970, +963, +20) are acceptable.
Give the vendor benefit of the doubt — false negatives (blocking a real business) are worse than false positives (flagging for human review).

Respond with EXACTLY one of these formats:
DECISION: APPROVE
RATIONALE: <brief reason>

DECISION: FLAG
RATIONALE: <specific concern>

DECISION: REJECT
RATIONALE: <specific reason>`;
}

function parseAdmissionResponse(raw: string): VendorAdmissionResult {
  const upper = raw.toUpperCase();

  let decision: VendorAdmissionResult['decision'] = 'FLAG';
  if (upper.includes('DECISION: APPROVE')) decision = 'APPROVE';
  else if (upper.includes('DECISION: REJECT')) decision = 'REJECT';

  const rationaleMatch = raw.match(/RATIONALE:\s*(.+)/i);
  const rationale = rationaleMatch?.[1]?.trim() ?? 'No rationale provided';

  return { decision, rationale };
}

/**
 * Deterministic fallback for admission review when Gemini is unavailable.
 * Applies simple heuristics: FLAG for non-Israeli phone prefix, APPROVE otherwise.
 */
function deterministicAdmission(input: VendorAdmissionInput): VendorAdmissionResult {
  const acceptedPrefixes = ['+972', '+970', '+961', '+963', '+20'];
  const phoneOk = acceptedPrefixes.some((p) => input.phoneCountryPrefix.startsWith(p));

  if (!phoneOk && input.phoneCountryPrefix) {
    return {
      decision: 'FLAG',
      rationale: `Phone country prefix ${input.phoneCountryPrefix} is outside accepted regions — routed to admin review`,
    };
  }

  return {
    decision: 'APPROVE',
    rationale: 'Passed deterministic admission checks (Gemini unavailable)',
  };
}
