import { describe, it, expect, vi, beforeEach } from "vitest";
import fastify from "fastify";
import { registerAdminPanelRoutes } from "../admin-panel-routes.js";
import { makeCtx } from "./helpers.js";

const makeAdminCtx = () => {
  const executeFn = vi.fn().mockResolvedValue([[]]);

  return makeCtx({
    db: { execute: executeFn } as any,
    requireAdmin: async (request: any) => {
      request.user = { sub: 1, id: 1, isAdmin: true };
    },
  });
};

const makeUnauthCtx = () => {
  return makeCtx({
    db: { execute: vi.fn().mockResolvedValue([[]]) } as any,
    requireAdmin: async (_req: any, reply: any) => {
      reply.code(403);
      throw new Error("Admin required");
    },
  });
};

describe("admin-panel-routes", () => {
  beforeEach(() => vi.clearAllMocks());

  // ────────────── GET /admin/stats ──────────────

  describe("GET /admin/stats", () => {
    it("retourne les stats dashboard", async () => {
      const ctx = makeAdminCtx();
      // Multiple DB queries: revenue today/yesterday (avatars, skins, cash, challenge, vip),
      // then new users, online players, active players, games, activity, recent
      (ctx.db.execute as any)
        .mockResolvedValueOnce([[{ total: 25.5 }]]) // avatar today
        .mockResolvedValueOnce([[{ total: 0 }]]) // ball today
        .mockResolvedValueOnce([[{ total: 2 }]]) // cash today
        .mockResolvedValueOnce([[{ total: 1 }]]) // challenge today
        .mockResolvedValueOnce([[{ total: 0 }]]) // vip today
        .mockResolvedValueOnce([[{ total: 10 }]]) // avatar yesterday
        .mockResolvedValueOnce([[{ total: 5 }]]) // ball yesterday
        .mockResolvedValueOnce([[{ total: 1 }]]) // cash yesterday
        .mockResolvedValueOnce([[{ total: 0 }]]) // challenge yesterday
        .mockResolvedValueOnce([[{ total: 0 }]]) // vip yesterday
        .mockResolvedValueOnce([[{ count: 3 }]]) // new users today
        .mockResolvedValueOnce([[{ count: 2 }]]) // new users yesterday
        .mockResolvedValueOnce([[{ count: 4 }]]) // online players now
        .mockResolvedValueOnce([[{ count: 2 }]]) // online players previous window
        .mockResolvedValueOnce([[{ count: 15 }]]) // active players
        .mockResolvedValueOnce([[{ count: 10 }]]) // active players yesterday
        .mockResolvedValueOnce([[{ count: 50 }]]) // games today
        .mockResolvedValueOnce([[{ count: 40 }]]) // games yesterday
        .mockResolvedValueOnce([[]]) // activity data
        .mockResolvedValue([[]]); // recent

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({ method: "GET", url: "/admin/stats" });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body).toHaveProperty("onlinePlayers");
      expect(body).toHaveProperty("onlineWindowMinutes");
      expect(body).toHaveProperty("activePlayers");
      expect(body).toHaveProperty("todayRevenue");
      expect(body).toHaveProperty("newUsers");
      expect(body).toHaveProperty("gamesPlayed");
    });
  });

  describe("GET /admin/stats/online-players", () => {
    it("retourne les joueurs vus récemment", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any).mockResolvedValueOnce([
        [
          {
            id: 7,
            display_name: "Player7",
            email: "p7@test.com",
            created_at: "2025-01-01",
            games_in_window: 3,
            last_game: "2025-01-01 10:00:00",
          },
        ],
      ]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "GET",
        url: "/admin/stats/online-players",
      });

      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(Array.isArray(body.players)).toBe(true);
      expect(body.players[0]).toHaveProperty("display_name", "Player7");
      expect(body).toHaveProperty("onlineWindowMinutes");
    });
  });

  // ────────────── GET /admin/players ──────────────

  describe("GET /admin/players", () => {
    it("retourne une liste paginée de joueurs", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any)
        .mockResolvedValueOnce([[{ total: 50 }]])
        .mockResolvedValue([[]]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "GET",
        url: "/admin/players?page=1&pageSize=10",
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body).toHaveProperty("total");
      expect(body).toHaveProperty("page");
      expect(body).toHaveProperty("pageSize");
    });

    it("supporte la recherche", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any)
        .mockResolvedValueOnce([[{ total: 1 }]])
        .mockResolvedValue([[]]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "GET",
        url: "/admin/players?search=test@mail.com",
      });
      expect(res.statusCode).toBe(200);
    });

    it("filtre par statut", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any)
        .mockResolvedValueOnce([[{ total: 5 }]])
        .mockResolvedValue([[]]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "GET",
        url: "/admin/players?status=banned",
      });
      expect(res.statusCode).toBe(200);
    });
  });

  // ────────────── GET /admin/players/:id ──────────────

  describe("GET /admin/players/:id", () => {
    it("retourne les détails d'un joueur", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any)
        .mockResolvedValueOnce([
          [
            {
              user_id: 42,
              username: "Player42",
              email: "p@test.com",
              is_guest: 0,
              created_at: "2025-01-01",
              banned_at: null,
              ban_reason: null,
              coins: 100,
              total_completions: 50,
              total_moves: 500,
              total_time: 36000,
              last_activity: "2025-06-01",
            },
          ],
        ])
        .mockResolvedValueOnce([[{ last_login: "2025-06-01" }]])
        .mockResolvedValueOnce([[{ level: 30 }]])
        .mockResolvedValueOnce([[{ count: 80 }]])
        .mockResolvedValueOnce([[]]) // avatar packs
        .mockResolvedValueOnce([[]]) // skin packs
        .mockResolvedValue([[]]); // recent activity

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "GET",
        url: "/admin/players/42",
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body.user_id).toBe(42);
      expect(body.username).toBe("Player42");
    });

    it("retourne 404 pour un joueur inexistant", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any).mockResolvedValue([[]]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "GET",
        url: "/admin/players/9999",
      });
      expect(res.statusCode).toBe(404);
    });

    it("retourne 400 pour un id invalide", async () => {
      const app = fastify();
      await registerAdminPanelRoutes(app, makeAdminCtx());

      const res = await app.inject({
        method: "GET",
        url: "/admin/players/abc",
      });
      expect(res.statusCode).toBe(400);
    });
  });

  // ────────────── POST /admin/players/:id/ban ──────────────

  describe("POST /admin/players/:id/ban", () => {
    it("bannit un joueur normal", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any)
        .mockResolvedValueOnce([[{ id: 42, is_admin: 0, banned_at: null }]])
        .mockResolvedValue([{ affectedRows: 1 }]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "POST",
        url: "/admin/players/42/ban",
        payload: { reason: "Triche", duration: "7d" },
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body.success).toBe(true);
    });

    it("refuse de bannir un admin", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any).mockResolvedValueOnce([[{ id: 1, is_admin: 1 }]]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "POST",
        url: "/admin/players/1/ban",
        payload: { reason: "Test" },
      });
      expect(res.statusCode).toBe(403);
    });

    it("retourne 404 pour joueur inexistant", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any).mockResolvedValue([[]]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "POST",
        url: "/admin/players/9999/ban",
        payload: { reason: "Test" },
      });
      expect(res.statusCode).toBe(404);
    });
  });

  // ────────────── POST /admin/players/:id/unban ──────────────

  describe("POST /admin/players/:id/unban", () => {
    it("débannit un joueur", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any)
        .mockResolvedValueOnce([[{ id: 42, banned_at: new Date() }]])
        .mockResolvedValue([{ affectedRows: 1 }]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "POST",
        url: "/admin/players/42/unban",
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body.success).toBe(true);
    });
  });

  // ────────────── POST /admin/db/reset ──────────────

  describe("POST /admin/db/reset", () => {
    it("refuse sans confirmation RESET", async () => {
      const app = fastify();
      await registerAdminPanelRoutes(app, makeAdminCtx());

      const res = await app.inject({
        method: "POST",
        url: "/admin/db/reset",
        payload: { confirm: "wrong" },
      });
      expect(res.statusCode).toBe(400);
    });
  });

  // ────────────── GET /admin/analytics ──────────────

  describe("GET /admin/analytics", () => {
    it("retourne les analytics", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any)
        .mockResolvedValueOnce([[{ total: 50 }]]) // revenue (current)
        .mockResolvedValueOnce([[{ total: 40 }]]) // revenue (previous)
        .mockResolvedValueOnce([[{ count: 10 }]]) // users (current)
        .mockResolvedValueOnce([[{ count: 8 }]]) // users (previous)
        .mockResolvedValueOnce([[{ count: 2 }]]) // payers (current)
        .mockResolvedValueOnce([[{ count: 1 }]]) // payers (previous)
        .mockResolvedValueOnce([[{ games: 20, players: 10, avgTimeMs: 50000 }]]) // leaderboard (current)
        .mockResolvedValueOnce([[{ games: 10, players: 8, avgTimeMs: 60000 }]]) // leaderboard (previous)
        .mockResolvedValue([[]]); // top levels

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "GET",
        url: "/admin/analytics?period=7d",
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body).toHaveProperty("arpu");
      expect(body).toHaveProperty("conversionRate");
    });
  });

  // ────────────── GET /admin/logs ──────────────

  describe("GET /admin/logs", () => {
    it("retourne les logs paginés", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any)
        .mockResolvedValueOnce([[]]) // logs
        .mockResolvedValueOnce([[{ total: 0 }]])
        .mockResolvedValueOnce([[{ total: 0 }]])
        .mockResolvedValueOnce([[{ total: 0 }]])
        .mockResolvedValueOnce([[{ total: 0 }]])
        .mockResolvedValue([[{ total: 0 }]]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "GET",
        url: "/admin/logs?page=1&pageSize=20",
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body).toHaveProperty("logs");
      expect(body).toHaveProperty("total");
    });
  });

  // ────────────── DELETE /admin/players/:id ──────────────

  describe("DELETE /admin/players/:id", () => {
    it("refuse de supprimer un admin", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any).mockResolvedValueOnce([
        [
          {
            id: 1,
            is_admin: 1,
            display_name: "Admin",
            email: "admin@test.com",
          },
        ],
      ]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "DELETE",
        url: "/admin/players/1",
      });
      expect(res.statusCode).toBe(403);
    });

    it("retourne 404 pour joueur inexistant", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any).mockResolvedValue([[]]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "DELETE",
        url: "/admin/players/9999",
      });
      expect(res.statusCode).toBe(404);
    });
  });

  // ────────────── POST /admin/ban-email ──────────────

  describe("POST /admin/ban-email", () => {
    it("bannit par email valide", async () => {
      const ctx = makeAdminCtx();
      (ctx.db.execute as any).mockResolvedValue([{ affectedRows: 2 }]);

      const app = fastify();
      await registerAdminPanelRoutes(app, ctx);

      const res = await app.inject({
        method: "POST",
        url: "/admin/ban-email",
        payload: { email: "cheater@test.com", reason: "Multi-comptes" },
      });
      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.body);
      expect(body.success).toBe(true);
    });

    it("refuse un email invalide", async () => {
      const app = fastify();
      await registerAdminPanelRoutes(app, makeAdminCtx());

      const res = await app.inject({
        method: "POST",
        url: "/admin/ban-email",
        payload: { email: "not-an-email" },
      });
      expect(res.statusCode).toBe(400);
    });
  });
});
