/**
 * POST /api/schedules/extract
 *   body: { text: string }
 *   returns: { ok, extracted, confidence, missing[], raw }
 *
 * V3 D (#266). One-shot LLM call that parses a user's natural-language
 * schedule request ("帮我每天 9 点查 mantis bug,发 telegram") into a structured
 * spec the ScheduleConfirmCard renders + the user confirms. Always
 * half-automatic: never auto-creates the schedule.
 *
 * Reuses the same API profile resolution the chat agent-loop uses, so the
 * user doesn't have to configure an extra key. Anthropic-only for MVP
 * (OpenAI path is a follow-up — adapter pattern is in place).
 */

import { NextResponse } from 'next/server';
import { loadSettings } from '@/lib/settings';
import { inferAdapter, pickApiKey, pickBaseUrl } from '@/lib/chat/agent-loop';
import { makeAnthropicClient } from '@/lib/chat/llm/anthropic';
import { listAgents } from '@/lib/agents';
import { scanProjects } from '@/lib/projects';

interface Extracted {
  name: string;
  prompt_name: string;
  prompt: string;
  agent: string | null;
  skills: string[];
  project: string | null;
  trigger: {
    kind: 'period' | 'cron' | 'once' | 'manual';
    interval_minutes: number | null;
    cron: string | null;
    at_iso: string | null;
  };
  action: {
    kind: 'none' | 'chat' | 'email' | 'telegram';
    config: Record<string, unknown>;
  };
  confidence: number;
  missing: string[];
}

function buildSystem(now: string, availableAgents: string[], availableProjects: string[]): string {
  return `You are the Forge Schedule extractor. Given a user's natural-language request, output ONE JSON object describing the schedule. No prose, no markdown, no code fences — just the JSON object.

Current time (ISO UTC): ${now}
Available agent ids: ${availableAgents.length ? availableAgents.join(', ') : '(none configured)'}
Available Forge projects: ${availableProjects.length ? availableProjects.join(', ') : '(none)'}

Output schema:
{
  "name":        string,                   // Schedule display name (short, human, original language OK)
  "prompt_name": string,                   // Slug for prompts/<name>.yaml. lowercase, [a-z0-9-]
  "prompt":      string,                   // VERBATIM user prompt — do NOT rewrite or paraphrase
  "agent":       string|null,              // Match against Available agent ids, else null
  "skills":      string[],                 // Default ["ai-orchestration"] unless user named more
  "project":     string|null,              // Match against Available Forge projects, else null
  "trigger": {
    "kind":             "period"|"cron"|"once"|"manual",
    "interval_minutes": number|null,       // for period
    "cron":             string|null,       // for cron, standard 5-field
    "at_iso":           string|null        // for once, ISO timestamp
  },
  "action": {
    "kind":   "none"|"chat"|"email"|"telegram",
    "config": object                       // see below; leave sparse if user gave no detail
  },
  "confidence": number,                    // 0..1, how confident you are in the extraction
  "missing":    string[]                   // Required fields the user did not provide ("project", "action.chat_id", ...)
}

Hard rules:
1. "prompt" MUST be the user's verbatim wording. If the user said "查一下我今天分到的 bug", keep it EXACTLY in that language and tone. Never translate, summarize, or "improve" it.
2. Time mapping:
   - "每天 9 点" / "every day at 9am"  → cron "0 9 * * *"
   - "每周一上午 10 点"              → cron "0 10 * * 1"
   - "每 30 分钟" / "every 30 min"   → period, interval_minutes=30
   - "5 分钟后跑一次" / "in 5 minutes" → once, at_iso = current_time + 5 minutes
   - Ambiguous time → trigger.kind="manual", add "trigger" to missing
3. Action mapping:
   - "发我 Telegram" / "send to telegram" → kind=telegram, config can be empty {} (user fills chat_id later)
   - "发邮件给 X" / "email me at X"        → kind=email, config={"to": "..."} if X present
   - "回到聊天" / "reply in chat"          → kind=chat, config={} (session_id missing → add to missing)
   - No action mentioned                  → kind=none
4. project: try to match exactly (case-insensitive) against Available Forge projects. If user said a name not in the list, still put it in "project" and add a note via missing=["project"] so the UI flags it.
5. skills: include "ai-orchestration" by default. If the user mentioned memory ("记得"/"remember") add "temper-memory". If they mentioned project docs add "knowledge-base".
6. confidence:
   - 1.0 if every field is unambiguous
   - 0.7-0.9 if minor guesses made
   - <0.7 if the prompt is unclear or required fields had to be guessed

Output ONLY the JSON object. No surrounding text.`;
}

export async function POST(req: Request) {
  let body: any = {};
  try { body = await req.json(); } catch {}
  const text: string = typeof body?.text === 'string' ? body.text.trim() : '';
  if (!text) return NextResponse.json({ ok: false, error: 'text required' }, { status: 400 });

  const settings = loadSettings();
  const profiles = settings.apiProfiles || {};
  const candidates = Object.entries(profiles).filter(([_, p]: [string, any]) => {
    if (!p || p.enabled === false) return false;
    return (p.apiKey || '').length > 0;
  });
  if (candidates.length === 0) {
    return NextResponse.json({
      ok: false,
      error: 'No API profile with an API key. Add one under Settings → API Profiles.',
    }, { status: 400 });
  }
  const preferredId = settings.chatAgent || candidates[0]![0];
  const profile: any = profiles[preferredId] && profiles[preferredId].enabled !== false
    ? profiles[preferredId]
    : candidates[0]![1];
  const adapter = inferAdapter(profile.provider);
  if (adapter !== 'anthropic') {
    return NextResponse.json({
      ok: false,
      error: 'extractor currently supports anthropic profiles only (openai is a follow-up)',
    }, { status: 400 });
  }
  const apiKey = pickApiKey(profile, adapter);
  const baseUrl = pickBaseUrl(profile, adapter);
  const model = profile.model || 'claude-sonnet-4-6';

  const cliAgents = listAgents().filter((a: any) => a.backendType !== 'api');
  const availableAgentIds = cliAgents.map((a: any) => a.id);
  const availableProjectNames = scanProjects().map((p) => p.name);
  const now = new Date().toISOString();
  const system = buildSystem(now, availableAgentIds, availableProjectNames);

  let raw = '';
  try {
    const client = makeAnthropicClient(apiKey, baseUrl);
    const resp = await client.messages.create({
      model,
      max_tokens: 1500,
      system,
      messages: [{ role: 'user', content: text }],
    });
    for (const block of resp.content) {
      if (block.type === 'text') raw += block.text;
    }
  } catch (e: any) {
    return NextResponse.json({
      ok: false,
      error: `LLM call failed: ${e?.message || String(e)}`,
    }, { status: 502 });
  }

  // Tolerate code fences / leading prose. Find the first { and last } and parse what's between.
  const jsonText = sliceOutermostJson(raw);
  let extracted: Extracted;
  try {
    extracted = JSON.parse(jsonText) as Extracted;
  } catch (e: any) {
    return NextResponse.json({
      ok: false,
      error: `LLM returned invalid JSON: ${e?.message || String(e)}`,
      raw,
    }, { status: 502 });
  }

  // Normalize: defaults + minimal validation. The card lets the user fix anything.
  extracted.skills = Array.isArray(extracted.skills) ? extracted.skills.filter((s) => typeof s === 'string') : [];
  if (extracted.skills.length === 0) extracted.skills = ['ai-orchestration'];
  extracted.missing = Array.isArray(extracted.missing) ? extracted.missing.filter((s) => typeof s === 'string') : [];
  extracted.confidence = Number.isFinite(extracted.confidence) ? Math.max(0, Math.min(1, extracted.confidence)) : 0.5;

  return NextResponse.json({
    ok: true,
    extracted,
    confidence: extracted.confidence,
    missing: extracted.missing,
  });
}

function sliceOutermostJson(s: string): string {
  const first = s.indexOf('{');
  const last = s.lastIndexOf('}');
  if (first < 0 || last < 0 || last <= first) return s.trim();
  return s.slice(first, last + 1);
}
