import { z } from "zod";
import { NextResponse } from "next/server";
import { getSessionPayload } from "@/lib/auth/session";
import { prisma } from "@/lib/prisma";
import { callOpenAiJsonSchema } from "@/lib/ai/openai";
import { storeAiSuggestionFromEnvelope } from "@/lib/ai/suggestions";

const M1_PROMPT_VERSION = "m1_assist_v1";

const M1AiSuggestionItemSchema = z.object({
  questionId: z.string().min(1),
  suggestedAnswerOptionId: z.string().min(1),
  rationale: z.string().min(10).max(1_500),
  missingEvidence: z.array(z.string().min(2).max(240)).max(8).default([]),
  confidence: z.number().min(0).max(1).optional(),
});

const M1AiSuggestionResponseSchema = z.object({
  suggestions: z.array(M1AiSuggestionItemSchema).max(50),
  confidence: z.enum(["low", "medium", "high"]).optional(),
});

const M1AiSuggestionJsonSchema = {
  type: "object",
  additionalProperties: false,
  required: ["suggestions"],
  properties: {
    suggestions: {
      type: "array",
      items: {
        type: "object",
        additionalProperties: false,
        required: ["questionId", "suggestedAnswerOptionId", "rationale", "missingEvidence"],
        properties: {
          questionId: { type: "string" },
          suggestedAnswerOptionId: { type: "string" },
          rationale: { type: "string" },
          missingEvidence: {
            type: "array",
            items: { type: "string" },
          },
          confidence: { type: "number", minimum: 0, maximum: 1 },
        },
      },
    },
    confidence: { type: "string", enum: ["low", "medium", "high"] },
  },
} as const;

function parseString(value: unknown) {
  return typeof value === "string" ? value.trim() : "";
}

function parseResponseEvidence(rawValue: unknown): { notes: string; links: string[] } | null {
  if (!rawValue || typeof rawValue !== "object" || Array.isArray(rawValue)) {
    return null;
  }

  const value = rawValue as Record<string, unknown>;
  const notes = typeof value.notes === "string" ? value.notes.trim() : "";
  const links = Array.isArray(value.links)
    ? value.links
        .filter((entry): entry is string => typeof entry === "string")
        .map((entry) => entry.trim())
        .filter((entry) => entry.length > 0)
    : [];

  if (!notes && links.length === 0) {
    return null;
  }

  return {
    notes,
    links,
  };
}

function toConfidenceNumber(confidence: number | undefined, fallback: "low" | "medium" | "high" | null) {
  if (typeof confidence === "number" && Number.isFinite(confidence)) {
    return Math.max(0, Math.min(1, confidence));
  }

  if (fallback === "high") {
    return 0.9;
  }

  if (fallback === "medium") {
    return 0.65;
  }

  if (fallback === "low") {
    return 0.35;
  }

  return null;
}

export async function POST(request: Request) {
  const session = await getSessionPayload();

  if (!session) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const body = (await request.json().catch(() => ({}))) as Record<string, unknown>;
  const moduleKey = parseString(body.moduleKey);
  const requestedQuestionId = parseString(body.questionId);

  if (!moduleKey) {
    return NextResponse.json({ error: "moduleKey es requerido." }, { status: 400 });
  }

  const moduleRecord = await prisma.diagnosticModule.findUnique({
    where: { key: moduleKey },
    select: {
      id: true,
      key: true,
      name: true,
      questions: {
        orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
        select: {
          id: true,
          prompt: true,
          helpText: true,
          answerOptions: {
            orderBy: [{ sortOrder: "asc" }, { createdAt: "asc" }],
            select: {
              id: true,
              label: true,
            },
          },
        },
      },
    },
  });

  if (!moduleRecord) {
    return NextResponse.json({ error: "Modulo no encontrado." }, { status: 404 });
  }

  const questionIds = moduleRecord.questions.map((question) => question.id);
  const responses = questionIds.length
    ? await prisma.response.findMany({
        where: {
          userId: session.userId,
          questionId: {
            in: questionIds,
          },
        },
        select: {
          questionId: true,
          answerOptionId: true,
          evidence: true,
        },
      })
    : [];

  const responseByQuestionId = new Map(responses.map((item) => [item.questionId, item]));
  const targetQuestions = moduleRecord.questions.filter((question) => {
    if (requestedQuestionId) {
      return question.id === requestedQuestionId;
    }

    return !responseByQuestionId.has(question.id);
  });

  if (requestedQuestionId && !targetQuestions.length) {
    return NextResponse.json({ error: "questionId no pertenece al modulo solicitado." }, { status: 400 });
  }

  if (!targetQuestions.length) {
    return NextResponse.json({
      ok: true,
      moduleKey,
      suggestions: [],
      warnings: [{ code: "no_unanswered_questions", message: "No hay preguntas pendientes para sugerir en este modulo." }],
    });
  }

  const answeredContext = moduleRecord.questions
    .map((question) => {
      const response = responseByQuestionId.get(question.id);
      if (!response) {
        return null;
      }

      const selectedOption = question.answerOptions.find((option) => option.id === response.answerOptionId) ?? null;
      const evidence = parseResponseEvidence(response.evidence);

      return {
        questionId: question.id,
        prompt: question.prompt,
        selectedAnswerOptionId: selectedOption?.id ?? response.answerOptionId,
        selectedAnswerLabel: selectedOption?.label ?? "No identificada",
        evidenceNotes: evidence?.notes ?? "",
        evidenceLinks: evidence?.links ?? [],
      };
    })
    .filter((item): item is NonNullable<typeof item> => Boolean(item));

  const targetContext = targetQuestions.map((question) => ({
    questionId: question.id,
    prompt: question.prompt,
    helpText: question.helpText ?? "",
    options: question.answerOptions.map((option) => ({
      id: option.id,
      label: option.label,
    })),
  }));

  const systemPrompt = [
    "Eres un asistente experto en diagnostico organizacional para contratacion publica en Mexico.",
    "Genera sugerencias de respuesta para preguntas no contestadas sin inventar evidencia.",
    "Debes responder solamente JSON valido.",
    "El idioma de salida debe ser espanol.",
  ].join(" ");

  const userPrompt = [
    `Modulo: ${moduleRecord.name} (${moduleRecord.key})`,
    "Respuestas ya contestadas por la organizacion:",
    JSON.stringify(answeredContext),
    "",
    "Preguntas objetivo a sugerir:",
    JSON.stringify(targetContext),
    "",
    "Instrucciones:",
    "- Para cada pregunta objetivo devuelve questionId y suggestedAnswerOptionId usando solo IDs listados en options.",
    "- rationale debe explicar por que esa opcion es la mas probable segun contexto.",
    "- missingEvidence debe listar evidencia que ayudaria a confirmar o corregir la sugerencia.",
    "- confidence debe ir de 0 a 1.",
  ].join("\n");

  const envelope = await callOpenAiJsonSchema({
    promptVersion: M1_PROMPT_VERSION,
    systemPrompt,
    userPrompt,
    outputSchema: M1AiSuggestionResponseSchema,
    schemaName: "m1_question_suggestions",
    jsonSchema: M1AiSuggestionJsonSchema as unknown as Record<string, unknown>,
    model: process.env.OPENAI_M1_MODEL?.trim() || undefined,
  });

  const targetQuestionById = new Map(
    targetQuestions.map((question) => [
      question.id,
      {
        id: question.id,
        optionIds: new Set(question.answerOptions.map((option) => option.id)),
      },
    ]),
  );

  const aiSuggestions = envelope.data?.suggestions ?? [];
  const normalizedSuggestions = aiSuggestions
    .map((item) => {
      const question = targetQuestionById.get(item.questionId);
      if (!question || !question.optionIds.has(item.suggestedAnswerOptionId)) {
        return null;
      }

      return item;
    })
    .filter((item): item is z.infer<typeof M1AiSuggestionItemSchema> => Boolean(item));

  const persisted = await Promise.all(
    normalizedSuggestions.map(async (item) => {
      const saved = await storeAiSuggestionFromEnvelope({
        userId: session.userId,
        moduleKey: "M1",
        featureKey: "diagnostic_answer_suggestion",
        subjectType: "question",
        subjectId: item.questionId,
        inputForHash: {
          moduleKey,
          questionId: item.questionId,
          answeredContext,
          targetQuestion: targetContext.find((question) => question.questionId === item.questionId) ?? null,
        },
        envelope,
        responsePayload: item,
      });

      return {
        ...item,
        suggestionId: saved.suggestionId,
      };
    }),
  );

  return NextResponse.json({
    ok: true,
    moduleKey,
    suggestions: persisted.map((item) => ({
      questionId: item.questionId,
      suggestedAnswerOptionId: item.suggestedAnswerOptionId,
      rationale: item.rationale,
      missingEvidence: item.missingEvidence,
      confidence: toConfidenceNumber(item.confidence, envelope.confidence),
      suggestionId: item.suggestionId,
    })),
    meta: {
      engine: envelope.engine,
      model: envelope.model,
      usage: envelope.usage,
      warnings: envelope.warnings,
      confidence: envelope.confidence,
    },
  });
}
