import { Router } from "express";
import { z } from "zod";
import { City } from "@/db/models";
import { asyncHandler, AppError } from "@/lib/errors";
import { requireAuth, requireRoles } from "@/middleware/auth";
import { toPlain } from "@/lib/serialize";
import { findCitiesByQuery } from "@/lib/cities";
import { isCitiesMysql } from "@/db/activeDatabase";
import {
  cityRowToApi,
  createCityMysql,
  listCitiesMysql,
  softDeleteCityMysql,
  updateCityMysql,
} from "@/db/mysql/cities";

export const citiesRouter = Router();

function mapCity(c: any) {
  if (c && typeof c === "object" && "nameAr" in c && "aliases" in c && !c.name_ar) {
    // Already shaped (mysql api / lean with aliases)
    const plain = toPlain(c);
    return {
      ...plain,
      _id: plain._id || plain.id,
      id: plain.id || plain._id,
      name: plain.nameEn || plain.name,
      aliases: plain.aliases || [],
      isTourist: Boolean(plain.isTourist),
    };
  }
  const plain = toPlain(c);
  return {
    ...plain,
    name: plain.nameEn,
    aliases: plain.aliases || [],
    isTourist: Boolean(plain.isTourist),
  };
}

citiesRouter.get(
  "/",
  asyncHandler(async (req, res) => {
    const q = z
      .object({
        q: z.string().optional(),
        tourist: z.enum(["true", "false"]).optional(),
      })
      .parse(req.query);

    if (isCitiesMysql()) {
      let cities;
      if (q.q) {
        cities = await findCitiesByQuery(q.q);
      } else {
        cities = (await listCitiesMysql({ touristOnly: q.tourist === "true" })).map(cityRowToApi);
      }
      res.json({ cities: cities.map(mapCity), _db: "mysql" });
      return;
    }

    let cities;
    if (q.q) {
      cities = await findCitiesByQuery(q.q);
    } else {
      const filter: Record<string, any> = { deletedAt: null };
      if (q.tourist === "true") filter.isTourist = true;
      cities = await City.find(filter).sort({ isTourist: -1, nameEn: 1 }).lean();
    }

    res.json({ cities: cities.map(mapCity), _db: "mongodb" });
  }),
);

citiesRouter.post(
  "/",
  requireAuth,
  requireRoles("ADMIN"),
  asyncHandler(async (req, res) => {
    const body = z
      .object({
        nameAr: z.string().min(2),
        nameEn: z.string().min(2),
        slug: z.string().min(2),
        country: z.string().default("Tunisia"),
        region: z.string().optional(),
        isTourist: z.boolean().optional(),
        aliases: z.array(z.string()).optional(),
        blurbEn: z.string().optional(),
        blurbAr: z.string().optional(),
        imageUrl: z.string().min(1).optional(),
      })
      .parse(req.body);

    if (isCitiesMysql()) {
      const city = await createCityMysql(body);
      res.status(201).json({ city: mapCity(cityRowToApi(city)), _db: "mysql" });
      return;
    }

    const city = await City.create(body);
    res.status(201).json({ city: mapCity(city.toObject()), _db: "mongodb" });
  }),
);

citiesRouter.patch(
  "/:id",
  requireAuth,
  requireRoles("ADMIN"),
  asyncHandler(async (req, res) => {
    const body = z
      .object({
        nameAr: z.string().min(2).optional(),
        nameEn: z.string().min(2).optional(),
        slug: z.string().min(2).optional(),
        country: z.string().optional(),
        region: z.string().optional(),
        isTourist: z.boolean().optional(),
        aliases: z.array(z.string()).optional(),
        blurbEn: z.string().optional(),
        blurbAr: z.string().optional(),
        imageUrl: z.string().min(1).optional().nullable(),
      })
      .parse(req.body);

    if (isCitiesMysql()) {
      const city = await updateCityMysql(req.params.id, body);
      if (!city) throw new AppError(404, "City not found");
      res.json({ city: mapCity(cityRowToApi(city)), _db: "mysql" });
      return;
    }

    const city = await City.findByIdAndUpdate(req.params.id, { $set: body }, { new: true });
    if (!city) throw new AppError(404, "City not found");
    res.json({ city: mapCity(city.toObject()), _db: "mongodb" });
  }),
);

citiesRouter.delete(
  "/:id",
  requireAuth,
  requireRoles("ADMIN"),
  asyncHandler(async (req, res) => {
    if (isCitiesMysql()) {
      await softDeleteCityMysql(req.params.id);
      res.json({ ok: true, _db: "mysql" });
      return;
    }

    await City.findByIdAndUpdate(req.params.id, { $set: { deletedAt: new Date() } });
    res.json({ ok: true, _db: "mongodb" });
  }),
);
