import { ErrorCode, type SignupInput } from "@batch-cooking/shared";
import { faker } from "@faker-js/faker";
import { expect } from "chai";
import request from "supertest";
import { createApp } from "../src/app.js";
import { env } from "../src/config/env.js";
import { prisma } from "../src/db/prisma.js";
import { hashAdminPassword } from "../src/modules/admin/admin-auth.service.js";
import { resetDatabase } from "../test-support/reset-db.js";

/** See `auth.test.ts` — same rationale for generating rather than hardcoding. */
function buildSignupPayload(): SignupInput {
  const firstName = faker.person.firstName();
  const lastName = faker.person.lastName();
  return {
    firstName,
    lastName,
    email: faker.internet.email({ firstName, lastName }).toLowerCase(),
    password: faker.internet.password({ length: 16 }),
  };
}

/** Inserts an `AdminUser` straight into the DB (no signup route exists) and returns its plaintext password. */
async function seedAdmin(): Promise<{ email: string; password: string }> {
  const email = faker.internet.email().toLowerCase();
  const password = faker.internet.password({ length: 16 });
  await prisma.adminUser.create({
    data: { email, name: faker.person.fullName(), passwordHash: await hashAdminPassword(password) },
  });
  return { email, password };
}

/** The admin login/verify path signs a JWT — self-skip those cases when no `ADMIN_JWT_SECRET` is configured (same posture as `tech-step-worker.routes.test.ts` with `INTERNAL_WORKER_SECRET`). */
const adminSecretConfigured = env.ADMIN_JWT_SECRET !== undefined;

describe("Admin auth", () => {
  const app = createApp();

  beforeEach(async () => {
    await resetDatabase();
  });

  after(async () => {
    await prisma.$disconnect();
  });

  describe("POST /admin/auth/login", () => {
    it("rejects a missing body with 400 VALIDATION_ERROR", async () => {
      const res = await request(app).post("/admin/auth/login").send({});
      expect(res.status).to.equal(400);
      expect(res.body.code).to.equal(ErrorCode.VALIDATION_ERROR);
    });

    it("rejects an unknown email with 401 INVALID_CREDENTIALS", async () => {
      const res = await request(app)
        .post("/admin/auth/login")
        .send({ email: "nobody@example.com", password: "whatever" });
      expect(res.status).to.equal(401);
      expect(res.body.code).to.equal(ErrorCode.INVALID_CREDENTIALS);
    });

    it("rejects a wrong password with 401 INVALID_CREDENTIALS", async () => {
      const { email } = await seedAdmin();
      const res = await request(app)
        .post("/admin/auth/login")
        .send({ email, password: "not-the-password" });
      expect(res.status).to.equal(401);
      expect(res.body.code).to.equal(ErrorCode.INVALID_CREDENTIALS);
    });

    it("logs in with correct credentials, sets the admin cookie, stamps lastLoginAt, never leaks the hash", async function () {
      if (!adminSecretConfigured) {
        // biome-ignore lint/suspicious/noExplicitAny: mocha's `this.skip()` isn't typed without @types/mocha (not a dependency here).
        (this as any).skip();
        return;
      }
      const { email, password } = await seedAdmin();

      const res = await request(app).post("/admin/auth/login").send({ email, password });

      expect(res.status).to.equal(200);
      expect(res.body.email).to.equal(email);
      expect(res.body).to.not.have.property("passwordHash");
      expect(res.body).to.not.have.property("tokenVersion");
      expect(res.body.lastLoginAt).to.be.a("string");

      const setCookie = res.headers["set-cookie"];
      expect(Array.isArray(setCookie) ? setCookie.join(";") : String(setCookie)).to.include(
        env.ADMIN_COOKIE_NAME,
      );

      const stored = await prisma.adminUser.findUniqueOrThrow({ where: { email } });
      expect(stored.lastLoginAt).to.not.equal(null);
    });
  });

  describe("GET /admin/auth/me", () => {
    it("rejects a request with no admin cookie with 401 NOT_AUTHENTICATED", async () => {
      const res = await request(app).get("/admin/auth/me");
      expect(res.status).to.equal(401);
      expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
    });

    it("returns the admin behind a valid admin session", async function () {
      if (!adminSecretConfigured) {
        // biome-ignore lint/suspicious/noExplicitAny: see above.
        (this as any).skip();
        return;
      }
      const { email, password } = await seedAdmin();
      const agent = request.agent(app);
      await agent.post("/admin/auth/login").send({ email, password });

      const res = await agent.get("/admin/auth/me");
      expect(res.status).to.equal(200);
      expect(res.body.email).to.equal(email);
    });

    it("stops returning the admin after logout", async function () {
      if (!adminSecretConfigured) {
        // biome-ignore lint/suspicious/noExplicitAny: see above.
        (this as any).skip();
        return;
      }
      const { email, password } = await seedAdmin();
      const agent = request.agent(app);
      await agent.post("/admin/auth/login").send({ email, password });

      const logoutRes = await agent.post("/admin/auth/logout");
      expect(logoutRes.status).to.equal(204);

      const meRes = await agent.get("/admin/auth/me");
      expect(meRes.status).to.equal(401);
    });

    it("does not accept an end-user session cookie as an admin session", async () => {
      // An ordinary user logs in (sets the `session` cookie), then tries the
      // admin surface with that same agent — `requireAdmin` reads a
      // different cookie entirely, so this must 401 regardless of whether
      // ADMIN_JWT_SECRET is configured.
      const agent = request.agent(app);
      await agent.post("/auth/signup").send(buildSignupPayload());

      const res = await agent.get("/admin/auth/me");
      expect(res.status).to.equal(401);
      expect(res.body.code).to.equal(ErrorCode.NOT_AUTHENTICATED);
    });
  });
});
