import OpenAI from 'openai';
import { z } from 'zod';
import { getSystemPromptSuggestUtm } from '@/lib/db/repositories';
import { DEFAULT_SYSTEM_PROMPT_SUGGEST_UTM } from '@/lib/db/defaults';
import { slugify, prefillUtm, type UtmParams } from '@/modules/sending/lib/utm';
import { getOpenRouterKey, getOpenRouterKeyOptional } from "@/core/settings";

const DEFAULT_MODEL = 'anthropic/claude-sonnet-4.6';
const MAX_TOKENS = 1000;

// Sehr lenient — UTM-Felder sind freitext, die Konvention erzwingen wir post-hoc
// per slugify, nicht per Zod-Constraint. Zod-Längen-Limits sind defensiv, falls Modell
// abdriftet.
const UtmSuggestionSchema = z.object({
  source: z.string().min(1).max(80).optional(),
  medium: z.string().min(1).max(40).optional(),
  campaign: z.string().min(1).max(120).optional(),
  term: z.string().min(1).max(80).optional(),
  inferred: z.array(z.string()).optional(),
  reasoning: z.string().optional(),
});

export type UtmSuggestion = z.infer<typeof UtmSuggestionSchema>;

const SUGGEST_UTM_TOOL: OpenAI.Chat.Completions.ChatCompletionTool = {
  type: 'function',
  function: {
    name: 'suggest_utm',
    description: 'Liefert saubere UTM-Parameter (source/medium/campaign/term) aus Briefing-Kontext.',
    parameters: {
      type: 'object',
      properties: {
        source: { type: 'string', description: 'utm_source — Plattform/Sender, z.B. "newsletter-its". Lowercase, ASCII, Bindestriche.' },
        medium: { type: 'string', description: 'utm_medium — IMMER "email" für Newsletter-Sends.' },
        campaign: { type: 'string', description: 'utm_campaign — Slug mit Jahr-Monat-Suffix, z.B. "reaktivierung-q2-2026-05".' },
        term: { type: 'string', description: 'utm_term — Audience-Tag, z.B. "bestand". Optional.' },
        inferred: { type: 'array', items: { type: 'string' }, description: 'Felder die konventionsbasiert (nicht brief-basiert) gefüllt wurden.' },
        reasoning: { type: 'string', description: 'Kurzer Hinweis was nicht ableitbar war.' },
      },
    },
  },
};

export interface SuggestUtmInput {
  briefText: string;
  briefHtml?: string;
  workspaceSlug?: string;
  campaignName?: string;
  existing: UtmParams; // bereits gefüllte Felder — diese werden NIE überschrieben
}

export interface SuggestUtmResult {
  suggestions: UtmSuggestion;
  modelUsed: string;
  tokensIn?: number;
  tokensOut?: number;
  durationMs: number;
}

export async function suggestUtm(input: SuggestUtmInput): Promise<SuggestUtmResult> {
  const start = Date.now();

  if (!getOpenRouterKeyOptional()) {
    return {
      suggestions: produceStubSuggestions(input),
      modelUsed: 'stub-no-api-key',
      durationMs: Date.now() - start,
    };
  }

  const model = process.env.OPENROUTER_MODEL ?? DEFAULT_MODEL;
  const client = new OpenAI({
    baseURL: 'https://openrouter.ai/api/v1',
    apiKey: getOpenRouterKey(),
    defaultHeaders: {
      'HTTP-Referer': process.env.OPENROUTER_APP_URL ?? 'http://localhost:3000',
      'X-Title': process.env.OPENROUTER_APP_TITLE ?? 'MailForge',
    },
  });

  const filledKeys = Object.entries(input.existing)
    .filter(([, v]) => typeof v === 'string' && v.trim().length > 0)
    .map(([k]) => k);

  const userMsg = [
    `# Briefing-Kontext`,
    input.workspaceSlug ? `- workspace: ${input.workspaceSlug}` : '',
    input.campaignName ? `- campaign_name: ${input.campaignName}` : '',
    '',
    `# Brief-Text`,
    input.briefText.trim() || '(leer)',
    '',
    filledKeys.length > 0 ? `# Bereits gefüllte UTM-Felder (NICHT überschreiben)\n${filledKeys.join(', ')}` : '',
    '',
    `Liefere UTM-Parameter via suggest_utm-Tool. Werte lowercase + ASCII + bindestrich-separiert. medium ist immer "email".`,
  ]
    .filter(Boolean)
    .join('\n');

  const completion = await client.chat.completions.create({
    model,
    max_tokens: MAX_TOKENS,
    messages: [
      { role: 'system', content: getSystemPromptSuggestUtm() || DEFAULT_SYSTEM_PROMPT_SUGGEST_UTM },
      { role: 'user', content: userMsg },
    ],
    tools: [SUGGEST_UTM_TOOL],
    tool_choice: { type: 'function', function: { name: 'suggest_utm' } },
  });

  const toolCall = completion.choices[0]?.message?.tool_calls?.[0];
  if (!toolCall || toolCall.type !== 'function') {
    throw new Error('Model returned no suggest_utm tool call');
  }

  const args = JSON.parse(toolCall.function.arguments);
  const parsed = UtmSuggestionSchema.safeParse(args);
  if (!parsed.success) {
    throw new Error(`Invalid UTM suggestion schema: ${parsed.error.issues.slice(0, 2).map((i) => i.message).join('; ')}`);
  }

  // Post-hoc Slug-Hygiene: das Modell soll's richtig liefern, aber wir erzwingen Konvention
  // sicherheitshalber. medium bleibt 1:1 (kann auch "email" oder ähnlich sein),
  // alle anderen Werte werden durch slugify gedreht.
  const cleaned: UtmSuggestion = { ...parsed.data };
  if (cleaned.source) cleaned.source = slugify(cleaned.source);
  if (cleaned.medium) cleaned.medium = cleaned.medium.toLowerCase().trim();
  if (cleaned.campaign) cleaned.campaign = slugify(cleaned.campaign);
  if (cleaned.term) cleaned.term = slugify(cleaned.term);

  // Existing-Schutz (zweiter Layer, defensiv)
  for (const k of filledKeys) {
    if (k in cleaned) delete (cleaned as Record<string, unknown>)[k];
  }

  return {
    suggestions: cleaned,
    modelUsed: model,
    tokensIn: completion.usage?.prompt_tokens,
    tokensOut: completion.usage?.completion_tokens,
    durationMs: Date.now() - start,
  };
}

/**
 * Stub-Fallback ohne API-Key — recycelt prefillUtm() aus utm.ts, damit auch ohne
 * LLM ein brauchbares Default-Set rauskommt.
 */
function produceStubSuggestions(input: SuggestUtmInput): UtmSuggestion {
  const prefilled = prefillUtm({
    workspaceSlug: input.workspaceSlug,
    campaignName: input.campaignName,
  });
  const inferred: string[] = ['medium'];
  const out: UtmSuggestion = {
    reasoning: '[Stub] Setze OPENROUTER_API_KEY für echte AI-Vorschläge',
  };
  if (prefilled.source && !input.existing.source) out.source = prefilled.source;
  if (prefilled.medium && !input.existing.medium) out.medium = prefilled.medium;
  if (prefilled.campaign && !input.existing.campaign) {
    out.campaign = prefilled.campaign;
    inferred.push('campaign');
  }
  if (prefilled.term && !input.existing.term) out.term = prefilled.term;
  out.inferred = inferred.filter((k) => k in out);
  return out;
}
