/**
 * TranslationKind — JobKind plugin for TRANSLATION LLM jobs.
 *
 * Translates deal title + description from Hebrew to English.
 * Upserts a row in deal_translations (locale='en') and updates
 * deals.translationStatus to COMPLETE or FAILED.
 *
 * Input: target_id = deal UUID, input_payload = {} (loadInput fetches from DB).
 * Output stored in output_payload matches TranslationResult JSON.
 */
import { sha256 } from '@platform-modules/util/crypto';
import { eq, and } from 'drizzle-orm';
import { z } from 'zod';
import type { DrizzleClient } from '@/server/db/client.js';
import { getLlmQueue } from '@/server/db/queries/llm-queues.js';
import { deals, dealTranslations } from '@/server/db/schema.js';
import { QUEUE_DEFAULT_PROMPTS } from '@/server/ai/queue-defaults.js';
import type { JobKind, JobRunDeps, LlmJobRow, JobCompletionMeta } from './types.js';
import {
  insertEnglishDealTranslation,
  updateEnglishDealTranslation,
} from '@/server/catalog/public/translations.js';
import { setTranslationStatus } from '@/server/db/queries/deals-extra.js';

// ─── Shapes ───────────────────────────────────────────────────────────────────

export interface TranslationInput {
  dealId: string;
  titleHe: string;
  descriptionHe: string;
  titleHash: string;
  descriptionHash: string;
}

export interface TranslationResult {
  costUsd: number;
  modelName: string;
  translated: Array<{ key: string; value: string; sourceHash: string }>;
  promptTokens: number;
  completionTokens: number;
  totalTokens?: number;
}

const TranslationResultSchema = z.object({
  costUsd: z.number(),
  modelName: z.string(),
  translated: z.array(z.object({ key: z.string(), value: z.string(), sourceHash: z.string() })),
  promptTokens: z.number(),
  completionTokens: z.number(),
  totalTokens: z.number().optional(),
});

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

function slugify(title: string, dealId: string): string {
  const base = title
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
    .slice(0, 60);
  return `${base}-${dealId.slice(0, 8)}`;
}

function calcCostUsd(promptTokens: number, completionTokens: number): number {
  // Gemini flash-lite pricing: $0.075/1M input, $0.3/1M output
  return (promptTokens * 0.075 + completionTokens * 0.3) / 1_000_000;
}

function extractJson(text: string): Record<string, unknown> {
  // Strip markdown code fences if present
  const clean = text
    .replace(/```(?:json)?/gi, '')
    .replace(/```/g, '')
    .trim();
  return JSON.parse(clean) as Record<string, unknown>;
}

// ─── Implementation ───────────────────────────────────────────────────────────

export const translationKind: JobKind<TranslationInput, TranslationResult, TranslationResult> = {
  jobType: 'TRANSLATION',

  async loadInput(_deps: JobRunDeps, jobRow: LlmJobRow): Promise<TranslationInput> {
    const dealId = jobRow.targetId;
    const rows = await _deps.db
      .select({ title: deals.title, description: deals.description })
      .from(deals)
      .where(eq(deals.id, dealId))
      .limit(1);

    const deal = rows[0];
    if (!deal) throw new Error(`TRANSLATION_LOAD: deal ${dealId} not found`);

    const titleHe = deal.title ?? '';
    const descriptionHe = deal.description ?? '';

    return {
      dealId,
      titleHe,
      descriptionHe,
      titleHash: await sha256(titleHe),
      descriptionHash: await sha256(descriptionHe),
    };
  },

  async runProvider(
    deps: JobRunDeps,
    _jobRow: LlmJobRow,
    input: TranslationInput,
  ): Promise<TranslationResult> {
    const modelId = deps.model;

    const queue = await getLlmQueue(deps.db, 'translation');
    const systemInstruction = queue?.prompt || QUEUE_DEFAULT_PROMPTS.translation;

    const prompt = `${systemInstruction}

Fields to translate:
{
  "title": ${JSON.stringify(input.titleHe)},
  "description": ${JSON.stringify(input.descriptionHe)}
}`;

    const response = await deps.provider.generateText(deps.creds, {
      model: modelId,
      prompt,
      kind: 'TRANSLATION',
    });

    const rawText = response.text;
    const resolvedModel = response.model ?? modelId;
    const promptTokens = response.usage.promptTokens;
    const completionTokens = response.usage.completionTokens;

    let parsed: Record<string, unknown>;
    try {
      parsed = extractJson(rawText);
    } catch (parseErr) {
      throw new Error(`TRANSLATION_PARSE: invalid JSON from LLM: ${rawText.slice(0, 200)}`, {
        cause: parseErr,
      });
    }

    const titleEn = typeof parsed['title'] === 'string' ? parsed['title'] : null;
    const descriptionEn = typeof parsed['description'] === 'string' ? parsed['description'] : null;

    if (!titleEn || !descriptionEn) {
      throw new Error(
        `TRANSLATION_PARSE: missing title or description in LLM response: ${rawText.slice(0, 200)}`,
      );
    }

    return {
      costUsd: calcCostUsd(promptTokens, completionTokens),
      modelName: resolvedModel,
      translated: [
        { key: 'title', value: titleEn, sourceHash: input.titleHash },
        {
          key: 'description',
          value: descriptionEn,
          sourceHash: input.descriptionHash,
        },
      ],
      promptTokens,
      completionTokens,
      totalTokens: response.usage.totalTokens,
    };
  },

  hydrateResult(jobRow: LlmJobRow): TranslationResult {
    const parsed = TranslationResultSchema.parse(jobRow.outputPayload);
    return parsed;
  },

  async applyVerdict(
    deps: JobRunDeps,
    jobRow: LlmJobRow,
    result: TranslationResult,
  ): Promise<{ applied: boolean }> {
    const dealId = jobRow.targetId;

    const titleField = result.translated.find((f) => f.key === 'title');
    const descField = result.translated.find((f) => f.key === 'description');

    if (!titleField || !descField) {
      throw new Error(`TRANSLATION_APPLY: missing title/description fields in result`);
    }

    // Check if existing en translation has same source hashes → skip
    const existing = await deps.db
      .select({
        titleSourceHash: dealTranslations.titleSourceHash,
        descriptionSourceHash: dealTranslations.descriptionSourceHash,
        titleManualOverride: dealTranslations.titleManualOverride,
        descriptionManualOverride: dealTranslations.descriptionManualOverride,
      })
      .from(dealTranslations)
      .where(and(eq(dealTranslations.dealId, dealId), eq(dealTranslations.locale, 'en')))
      .limit(1);

    const existingRow = existing[0];
    if (
      existingRow &&
      existingRow.titleSourceHash === titleField.sourceHash &&
      existingRow.descriptionSourceHash === descField.sourceHash
    ) {
      // Source unchanged — no re-translation needed
      return { applied: false };
    }

    // Respect manual overrides: don't overwrite manually-set fields
    const writeTitle = existingRow?.titleManualOverride ? undefined : titleField.value;
    const writeDescription = existingRow?.descriptionManualOverride ? undefined : descField.value;

    // Slug is derived from the AI-translated title (or existing title if overridden)
    const slugTitle = writeTitle ?? titleField.value;
    const slug = slugify(slugTitle, dealId);
    const now = new Date();

    if (existingRow) {
      // Update existing row
      await updateEnglishDealTranslation(deps.db, dealId, {
        ...(writeTitle !== undefined ? { title: writeTitle } : {}),
        ...(writeDescription !== undefined ? { description: writeDescription } : {}),
        titleSourceHash: titleField.sourceHash,
        descriptionSourceHash: descField.sourceHash,
        modelId: result.modelName,
        status: 'OK',
        translatedAt: now,
        updatedAt: now,
      });
    } else {
      await insertEnglishDealTranslation(deps.db, {
        dealId,
        locale: 'en',
        slug,
        title: titleField.value,
        description: descField.value,
        pickupAddress: '',
        titleSourceHash: titleField.sourceHash,
        descriptionSourceHash: descField.sourceHash,
        modelId: result.modelName,
        status: 'OK',
        translatedAt: now,
      });
    }

    await setTranslationStatus(deps.db, dealId, 'COMPLETE');

    return { applied: true };
  },

  async onRetryExhausted(db: DrizzleClient, jobRow: LlmJobRow, _reason: string): Promise<void> {
    await setTranslationStatus(db, jobRow.targetId, 'FAILED');
  },

  extractCompletionMeta(result: unknown): JobCompletionMeta {
    const r = result as TranslationResult;
    return {
      modelName: r.modelName,
      promptTokens: r.promptTokens,
      completionTokens: r.completionTokens,
      totalTokens: r.totalTokens ?? r.promptTokens + r.completionTokens,
      costUsd: r.costUsd != null ? r.costUsd.toFixed(6) : undefined,
    };
  },
};
