import { Router } from "express";
import { Prisma } from "@prisma/client";
import { z } from "zod";
import { prisma } from "../prisma.js";
import { requireAuth } from "../middleware/auth.js";
import { HttpError } from "../lib/http-error.js";

export const studentsRouter = Router();

studentsRouter.use(requireAuth);

const CATEGORIAS = [
  "STEM_JR",
  "STEM_SR",
  "ROBOFUT",
  "DRONES",
  "TODO_TERRENO",
] as const;
const MODALIDADES = ["individual", "pareja"] as const;
const ESTADOS_PAGO = ["verificado", "sin_verificar", "sin_pago"] as const;
const ESTADOS_KIT = [
  "asignado",
  "empacado",
  "enviado",
  "recibido",
  "recogido_en_bootcamp",
] as const;

const filtersSchema = z.object({
  search: z.string().trim().max(120).optional(),
  colegio: z.string().trim().max(150).optional(),
  departamento: z.string().trim().max(80).optional(),
  categoria: z.enum(CATEGORIAS).optional(),
  modalidad: z.enum(MODALIDADES).optional(),
  estado_pago: z.enum(ESTADOS_PAGO).optional(),
  estado_kit: z.enum(ESTADOS_KIT).optional(),
  bootcamp: z.string().trim().max(120).optional(),
});

const listQuerySchema = filtersSchema.extend({
  page: z.coerce.number().int().min(1).default(1),
  pageSize: z.coerce.number().int().min(1).max(200).default(50),
});

type Filters = z.infer<typeof filtersSchema>;

type StudentRow = {
  estudiante_id: number;
  nombre_completo: string;
  correo: string | null;
  telefono: string | null;
  departamento: string | null;
  colegio: string | null;
  categoria_crea: string | null;
  modalidad_crea: string | null;
  talla_playera: string | null;
  kit_codigo: string | null;
  nombre_robot: string | null;
  kit_estado: string | null;
  bootcamp_recoge: string | null;
  estado_pago: string;
};

type StudentDetailRow = StudentRow & {
  nombres: string;
  apellido_1: string | null;
  apellido_2: string | null;
  fecha_nacimiento: Date | null;
  edad: number | null;
  genero: string | null;
  grado_escolar: string | null;
  nacionalidad: string | null;
  area_interes: string | null;
  categoria_interes_inicial: string | null;
  completo_form_inicial: number;
  curso_aprende: string | null;
  nota_aprende: string | null;
  apto_para_crea: number;
};

function buildFilters(q: Filters): Prisma.Sql[] {
  const conditions: Prisma.Sql[] = [
    Prisma.sql`categoria_crea IS NOT NULL`,
  ];
  if (q.colegio) conditions.push(Prisma.sql`colegio = ${q.colegio}`);
  if (q.departamento)
    conditions.push(Prisma.sql`departamento = ${q.departamento}`);
  if (q.categoria)
    conditions.push(Prisma.sql`categoria_crea = ${q.categoria}`);
  if (q.modalidad)
    conditions.push(Prisma.sql`modalidad_crea = ${q.modalidad}`);
  if (q.estado_pago)
    conditions.push(Prisma.sql`estado_pago = ${q.estado_pago}`);
  if (q.estado_kit) conditions.push(Prisma.sql`kit_estado = ${q.estado_kit}`);
  if (q.search) {
    const term = `%${q.search}%`;
    conditions.push(
      Prisma.sql`(nombre_completo LIKE ${term} OR correo LIKE ${term} OR kit_codigo LIKE ${term} OR nombre_robot LIKE ${term})`
    );
  }
  if (q.bootcamp)
    conditions.push(Prisma.sql`bootcamp_recoge = ${q.bootcamp}`);
  return conditions;
}

studentsRouter.get("/", async (req, res, next) => {
  try {
    const query = listQuerySchema.parse(req.query);
    const where = Prisma.join(buildFilters(query), " AND ");
    const offset = (query.page - 1) * query.pageSize;

    const rows = await prisma.$queryRaw<StudentRow[]>`
      SELECT
        estudiante_id, nombre_completo, correo, telefono,
        departamento, colegio,
        categoria_crea, modalidad_crea, talla_playera,
        kit_codigo, nombre_robot, kit_estado,
        bootcamp_recoge, estado_pago
      FROM v_estudiantes_completos
      WHERE ${where}
      ORDER BY nombre_completo
      LIMIT ${query.pageSize} OFFSET ${offset}
    `;

    const [{ total }] = await prisma.$queryRaw<[{ total: bigint }]>`
      SELECT COUNT(*) AS total
      FROM v_estudiantes_completos
      WHERE ${where}
    `;

    res.json({
      data: rows,
      pagination: {
        page: query.page,
        pageSize: query.pageSize,
        total: Number(total),
        totalPages: Math.ceil(Number(total) / query.pageSize),
      },
    });
  } catch (err) {
    next(err);
  }
});

studentsRouter.get("/filters", async (_req, res, next) => {
  try {
    const colegios = await prisma.$queryRaw<{ value: string; count: bigint }[]>`
      SELECT colegio AS value, COUNT(*) AS count
      FROM v_estudiantes_completos
      WHERE categoria_crea IS NOT NULL AND colegio IS NOT NULL
      GROUP BY colegio
      ORDER BY colegio
    `;
    const departamentos = await prisma.$queryRaw<
      { value: string; count: bigint }[]
    >`
      SELECT departamento AS value, COUNT(*) AS count
      FROM v_estudiantes_completos
      WHERE categoria_crea IS NOT NULL AND departamento IS NOT NULL
      GROUP BY departamento
      ORDER BY departamento
    `;
    const categorias = await prisma.$queryRaw<
      { value: string; count: bigint }[]
    >`
      SELECT categoria_crea AS value, COUNT(*) AS count
      FROM v_estudiantes_completos
      WHERE categoria_crea IS NOT NULL
      GROUP BY categoria_crea
      ORDER BY categoria_crea
    `;

    const bootcamps = await prisma.$queryRaw<{ value: string; count: bigint }[]>`
      SELECT bootcamp_recoge AS value, COUNT(*) AS count
      FROM v_estudiantes_completos
      WHERE categoria_crea IS NOT NULL AND bootcamp_recoge IS NOT NULL
      GROUP BY bootcamp_recoge
      ORDER BY bootcamp_recoge
    `;

    const toSerializable = (rows: { value: string; count: bigint }[]) =>
      rows.map((r) => ({ value: r.value, count: Number(r.count) }));

    res.json({
      colegios: toSerializable(colegios),
      departamentos: toSerializable(departamentos),
      categorias: toSerializable(categorias),
      bootcamps: toSerializable(bootcamps),
      modalidades: MODALIDADES,
      estados_pago: ESTADOS_PAGO,
      estados_kit: ESTADOS_KIT,
    });
  } catch (err) {
    next(err);
  }
});

studentsRouter.get("/export", async (req, res, next) => {
  try {
    const query = filtersSchema.parse(req.query);
    const where = Prisma.join(buildFilters(query), " AND ");

    const rows = await prisma.$queryRaw<StudentDetailRow[]>`
      SELECT
        estudiante_id, nombre_completo, nombres, apellido_1, apellido_2,
        correo, telefono, fecha_nacimiento, edad, genero, grado_escolar,
        nacionalidad, departamento, colegio, area_interes,
        categoria_interes_inicial, completo_form_inicial,
        curso_aprende, nota_aprende, apto_para_crea,
        categoria_crea, modalidad_crea, talla_playera,
        kit_codigo, nombre_robot, kit_estado, bootcamp_recoge, estado_pago
      FROM v_estudiantes_completos
      WHERE ${where}
      ORDER BY nombre_completo
    `;

    const headers = [
      "id",
      "nombre_completo",
      "nombres",
      "apellido_1",
      "apellido_2",
      "correo",
      "telefono",
      "fecha_nacimiento",
      "edad",
      "genero",
      "grado_escolar",
      "nacionalidad",
      "departamento",
      "colegio",
      "categoria_crea",
      "modalidad_crea",
      "talla_playera",
      "kit_codigo",
      "nombre_robot",
      "kit_estado",
      "bootcamp_recoge",
      "estado_pago",
      "curso_aprende",
      "nota_aprende",
    ];

    const escape = (v: unknown): string => {
      if (v === null || v === undefined) return "";
      const s =
        v instanceof Date
          ? v.toISOString().slice(0, 10)
          : String(v);
      if (/[",\n\r]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
      return s;
    };

    const lines = [
      headers.join(","),
      ...rows.map((r) =>
        [
          r.estudiante_id,
          r.nombre_completo,
          r.nombres,
          r.apellido_1,
          r.apellido_2,
          r.correo,
          r.telefono,
          r.fecha_nacimiento,
          r.edad,
          r.genero,
          r.grado_escolar,
          r.nacionalidad,
          r.departamento,
          r.colegio,
          r.categoria_crea,
          r.modalidad_crea,
          r.talla_playera,
          r.kit_codigo,
          r.nombre_robot,
          r.kit_estado,
          r.bootcamp_recoge,
          r.estado_pago,
          r.curso_aprende,
          r.nota_aprende,
        ]
          .map(escape)
          .join(",")
      ),
    ];

    const csv = "﻿" + lines.join("\r\n") + "\r\n";
    const filename = `estudiantes_${new Date().toISOString().slice(0, 10)}.csv`;

    res.setHeader("Content-Type", "text/csv; charset=utf-8");
    res.setHeader(
      "Content-Disposition",
      `attachment; filename="${filename}"`
    );
    res.send(csv);
  } catch (err) {
    next(err);
  }
});

// ─── INSCRIPCIÓN MANUAL A CREA ───────────────────────────────────────────────
// Misma lógica que balam_db/importar.py `_insert_kit()`: usa la función SQL
// siguiente_codigo_kit(categoria) para generar el correlativo y crea un kit
// nuevo + crea_inscripciones en una transacción.

const sinInscripcionQuerySchema = z.object({
  q: z.string().trim().min(1).max(120),
});

type EstudianteBasicoRow = {
  estudiante_id: number;
  nombre_completo: string;
  correo: string | null;
  colegio: string | null;
  departamento: string | null;
};

studentsRouter.get("/sin-inscripcion", async (req, res, next) => {
  try {
    const { q } = sinInscripcionQuerySchema.parse(req.query);
    const term = `%${q}%`;

    const rows = await prisma.$queryRaw<EstudianteBasicoRow[]>`
      SELECT
        e.id AS estudiante_id,
        CONCAT_WS(' ', e.nombres, e.apellido_1, e.apellido_2) AS nombre_completo,
        e.correo,
        col.nombre AS colegio,
        d.nombre AS departamento
      FROM estudiantes e
      LEFT JOIN colegios col ON col.id = e.colegio_id
      LEFT JOIN departamentos d ON d.id = e.departamento_id
      WHERE NOT EXISTS (
          SELECT 1 FROM crea_inscripciones ci WHERE ci.estudiante_id = e.id
        )
        AND (
          CONCAT_WS(' ', e.nombres, e.apellido_1, e.apellido_2) LIKE ${term}
          OR e.correo LIKE ${term}
        )
      ORDER BY nombre_completo
      LIMIT 20
    `;

    res.json({
      data: rows.map((r) => ({
        ...r,
        estudiante_id: Number(r.estudiante_id),
      })),
    });
  } catch (err) {
    next(err);
  }
});

const TALLAS = ["XS", "S", "M", "L", "XL", "XXL"] as const;

const tallaSchema = z
  .preprocess(
    (v) => (typeof v === "string" && v.trim() === "" ? null : v),
    z.enum(TALLAS).nullable()
  )
  .optional();

const inscripcionSchema = z
  .object({
    estudiante_id: z.coerce.number().int().positive(),
    categoria_codigo: z.enum(CATEGORIAS),
    modalidad: z.enum(MODALIDADES),
    talla_playera: tallaSchema,
    bootcamp_id: z.coerce.number().int().positive().nullable().optional(),
    pareja_estudiante_id: z.coerce
      .number()
      .int()
      .positive()
      .nullable()
      .optional(),
    talla_pareja: tallaSchema,
  })
  .refine(
    (v) =>
      v.modalidad !== "pareja" ||
      (v.pareja_estudiante_id !== null && v.pareja_estudiante_id !== undefined),
    {
      message: "Para modalidad 'pareja' debés elegir un compañero",
      path: ["pareja_estudiante_id"],
    }
  )
  .refine(
    (v) =>
      v.modalidad !== "pareja" ||
      v.pareja_estudiante_id !== v.estudiante_id,
    {
      message: "El estudiante y su pareja no pueden ser el mismo",
      path: ["pareja_estudiante_id"],
    }
  );

studentsRouter.post("/inscripcion", async (req, res, next) => {
  try {
    const body = inscripcionSchema.parse(req.body);

    const studentIds =
      body.modalidad === "pareja" && body.pareja_estudiante_id
        ? [body.estudiante_id, body.pareja_estudiante_id]
        : [body.estudiante_id];

    const students = await prisma.estudiantes.findMany({
      where: { id: { in: studentIds } },
      include: { crea_inscripciones: true },
    });

    if (students.length !== studentIds.length) {
      throw new HttpError(404, "Estudiante o compañero no encontrado");
    }
    for (const s of students) {
      if (s.crea_inscripciones) {
        throw new HttpError(
          409,
          `El estudiante "${s.nombres}" ya tiene una inscripción de Crea`
        );
      }
    }

    if (body.bootcamp_id) {
      const bc = await prisma.bootcamps.findUnique({
        where: { id: body.bootcamp_id },
      });
      if (!bc) throw new HttpError(404, "Bootcamp no encontrado");
    }

    const result = await prisma.$transaction(async (tx) => {
      const next = await tx.$queryRaw<{ codigo: string | null }[]>`
        SELECT siguiente_codigo_kit(${body.categoria_codigo}) AS codigo
      `;
      const codigo = next[0]?.codigo;
      if (!codigo) {
        throw new HttpError(
          500,
          `No se pudo generar el correlativo para la categoría ${body.categoria_codigo}`
        );
      }
      const newKit = await tx.kits.create({
        data: {
          codigo,
          categoria_codigo: body.categoria_codigo,
          estado: "asignado",
        },
      });

      let parejaId: number | null = null;
      let kitGoesToMain = true;

      if (body.modalidad === "pareja" && body.pareja_estudiante_id) {
        const e1 = Math.min(body.estudiante_id, body.pareja_estudiante_id);
        const e2 = Math.max(body.estudiante_id, body.pareja_estudiante_id);
        const created = await tx.parejas.create({
          data: { estudiante_1_id: e1, estudiante_2_id: e2 },
        });
        parejaId = created.id;
        kitGoesToMain = body.estudiante_id <= body.pareja_estudiante_id;
      }

      await tx.crea_inscripciones.create({
        data: {
          estudiante_id: body.estudiante_id,
          categoria_codigo: body.categoria_codigo,
          modalidad: body.modalidad,
          kit_id: kitGoesToMain ? newKit.id : null,
          pareja_id: parejaId,
          bootcamp_id: body.bootcamp_id ?? null,
          talla_playera: body.talla_playera ?? null,
          sincronizado_form: false,
        },
      });

      if (body.modalidad === "pareja" && body.pareja_estudiante_id) {
        await tx.crea_inscripciones.create({
          data: {
            estudiante_id: body.pareja_estudiante_id,
            categoria_codigo: body.categoria_codigo,
            modalidad: body.modalidad,
            kit_id: kitGoesToMain ? null : newKit.id,
            pareja_id: parejaId,
            bootcamp_id: body.bootcamp_id ?? null,
            talla_playera: body.talla_pareja ?? null,
            sincronizado_form: false,
          },
        });
      }

      return { kit_codigo: newKit.codigo };
    });

    const rows = await prisma.$queryRaw<StudentDetailRow[]>`
      SELECT *
      FROM v_estudiantes_completos
      WHERE estudiante_id = ${body.estudiante_id} AND categoria_crea IS NOT NULL
      LIMIT 1
    `;
    if (rows.length === 0) {
      throw new HttpError(500, "Inscripción creada pero no se pudo recuperar");
    }
    res.status(201).json({
      student: rows[0],
      kit_codigo: result.kit_codigo,
    });
  } catch (err) {
    next(err);
  }
});

const emptyToNull = (v: unknown) =>
  typeof v === "string" && v.trim() === "" ? null : v;

const updateStudentSchema = z.object({
  correo: z
    .preprocess(emptyToNull, z.string().trim().email("Correo inválido").max(120).nullable())
    .optional(),
  telefono: z
    .preprocess(emptyToNull, z.string().trim().max(30).nullable())
    .optional(),
  nombre_robot: z
    .preprocess(emptyToNull, z.string().trim().max(100).nullable())
    .optional(),
});

studentsRouter.patch("/:id", async (req, res, next) => {
  try {
    const id = z.coerce.number().int().positive().parse(req.params.id);
    const body = updateStudentSchema.parse(req.body);

    const student = await prisma.estudiantes.findUnique({
      where: { id },
      include: { crea_inscripciones: true },
    });
    if (!student) throw new HttpError(404, "Estudiante no encontrado");

    const inscripcion = student.crea_inscripciones;
    const kitId = inscripcion?.kit_id ?? null;

    if (body.nombre_robot !== undefined && kitId === null) {
      throw new HttpError(
        400,
        "Este estudiante no tiene kit asignado; no se puede editar el nombre del robot"
      );
    }

    await prisma.$transaction(async (tx) => {
      const estudianteData: { correo?: string | null; telefono?: string | null } = {};
      if (body.correo !== undefined) estudianteData.correo = body.correo;
      if (body.telefono !== undefined) estudianteData.telefono = body.telefono;
      if (Object.keys(estudianteData).length > 0) {
        await tx.estudiantes.update({ where: { id }, data: estudianteData });
      }

      if (body.nombre_robot !== undefined && kitId !== null) {
        await tx.kits.update({
          where: { id: kitId },
          data: { nombre_robot: body.nombre_robot },
        });
      }
    });

    const rows = await prisma.$queryRaw<StudentDetailRow[]>`
      SELECT *
      FROM v_estudiantes_completos
      WHERE estudiante_id = ${id} AND categoria_crea IS NOT NULL
      LIMIT 1
    `;
    if (rows.length === 0) {
      throw new HttpError(404, "Estudiante no encontrado");
    }
    res.json({ student: rows[0] });
  } catch (err) {
    next(err);
  }
});

studentsRouter.get("/:id", async (req, res, next) => {
  try {
    const id = z.coerce.number().int().positive().parse(req.params.id);

    const rows = await prisma.$queryRaw<StudentDetailRow[]>`
      SELECT *
      FROM v_estudiantes_completos
      WHERE estudiante_id = ${id} AND categoria_crea IS NOT NULL
      LIMIT 1
    `;

    if (rows.length === 0) {
      throw new HttpError(404, "Estudiante no encontrado");
    }

    res.json({ student: rows[0] });
  } catch (err) {
    next(err);
  }
});
