/**
 * Auth route edge-case tests — login failures, session expiry,
 * password-change side effects, register validation.
 */

import { eq } from "drizzle-orm";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { db, schema } from "../../../apps/api/src/db/index.js";
import { buildTestApp, loginAsAdmin, type TestApp } from "../test-server.js";

let testApp: TestApp;
let adminToken: string;

const uid = () => `auth_test_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;

beforeAll(async () => {
  testApp = await buildTestApp();
  adminToken = await loginAsAdmin(testApp.app);
}, 30_000);

afterAll(async () => {
  await testApp.cleanup();
}, 10_000);

// Helper: register a user, clear mustChangePassword, return { username, password }
async function createUser(
  opts: { role?: string; team?: string } = {},
): Promise<{ username: string; password: string; id: string }> {
  const username = uid();
  const password = "ValidPass1";
  const res = await testApp.app.inject({
    method: "POST",
    url: "/api/auth/register",
    headers: { authorization: `Bearer ${adminToken}` },
    payload: { username, password, ...opts },
  });
  const body = JSON.parse(res.body);
  if (res.statusCode !== 201) {
    throw new Error(`createUser failed: ${res.statusCode} ${res.body}`);
  }
  await db
    .update(schema.users)
    .set({ mustChangePassword: false })
    .where(eq(schema.users.username, username));
  return { username, password, id: body.id };
}

// Helper: login and return token
async function loginAs(username: string, password: string): Promise<string> {
  const res = await testApp.app.inject({
    method: "POST",
    url: "/api/auth/login",
    payload: { username, password },
  });
  const body = JSON.parse(res.body);
  if (!body.token) throw new Error(`loginAs failed: ${res.body}`);
  return body.token as string;
}

// ═══════════════════════════════════════════════════════════════════════════
// LOGIN FAILURES
// ═══════════════════════════════════════════════════════════════════════════
describe("Login failures", () => {
  it("empty body returns 400", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/login",
      payload: {},
    });
    expect(res.statusCode).toBe(400);
  });

  it("missing username returns 400", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/login",
      payload: { password: "Anything1" },
    });
    expect(res.statusCode).toBe(400);
  });

  it("missing password returns 400", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/login",
      payload: { username: "admin" },
    });
    expect(res.statusCode).toBe(400);
  });

  it("unknown username returns 401", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/login",
      payload: { username: `nonexistent_${Date.now()}`, password: "Whatever1" },
    });
    expect(res.statusCode).toBe(401);
  });

  it("wrong password returns 401", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/login",
      payload: { username: "admin", password: "WrongPass1" },
    });
    expect(res.statusCode).toBe(401);
  });

  it("failed logins generate LOGIN_FAILED audit events", async () => {
    const marker = uid();
    // Trigger a failed login with a unique username
    await testApp.app.inject({
      method: "POST",
      url: "/api/auth/login",
      payload: { username: marker, password: "Whatever1" },
    });

    const res = await testApp.app.inject({
      method: "GET",
      url: "/api/v1/audit-log?action=LOGIN_FAILED&limit=50",
      headers: { authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body);
    const match = body.entries.find(
      (e: any) => e.action === "LOGIN_FAILED" && e.details?.username === marker,
    );
    expect(match).toBeDefined();
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// SESSION EDGE CASES
// ═══════════════════════════════════════════════════════════════════════════
describe("Session edge cases", () => {
  it("no token on session endpoint returns 401", async () => {
    const res = await testApp.app.inject({
      method: "GET",
      url: "/api/auth/session",
    });
    expect(res.statusCode).toBe(401);
  });

  it("expired session token returns 401", async () => {
    // Login to get a valid session
    const token = await loginAs("admin", "Adminpass1");

    // Manually expire the session in the DB
    await db
      .update(schema.sessions)
      .set({ expiresAt: new Date(Date.now() - 60_000) })
      .where(eq(schema.sessions.id, token));

    const res = await testApp.app.inject({
      method: "GET",
      url: "/api/auth/session",
      headers: { authorization: `Bearer ${token}` },
    });
    expect(res.statusCode).toBe(401);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// PASSWORD CHANGE SIDE EFFECTS
// ═══════════════════════════════════════════════════════════════════════════
describe("Password change side effects", () => {
  it("changing password invalidates other sessions", async () => {
    const { username, password } = await createUser();

    // Create two sessions
    const token1 = await loginAs(username, password);
    const token2 = await loginAs(username, password);

    // Verify both sessions work
    const check1 = await testApp.app.inject({
      method: "GET",
      url: "/api/auth/session",
      headers: { authorization: `Bearer ${token1}` },
    });
    expect(check1.statusCode).toBe(200);

    const check2 = await testApp.app.inject({
      method: "GET",
      url: "/api/auth/session",
      headers: { authorization: `Bearer ${token2}` },
    });
    expect(check2.statusCode).toBe(200);

    // Change password via session 1
    const changeRes = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/change-password",
      headers: { authorization: `Bearer ${token1}` },
      payload: { currentPassword: password, newPassword: "NewValid1" },
    });
    expect(changeRes.statusCode).toBe(200);

    // Session 1 should still work (it's the current session)
    const after1 = await testApp.app.inject({
      method: "GET",
      url: "/api/auth/session",
      headers: { authorization: `Bearer ${token1}` },
    });
    expect(after1.statusCode).toBe(200);

    // Session 2 should now be invalid
    const after2 = await testApp.app.inject({
      method: "GET",
      url: "/api/auth/session",
      headers: { authorization: `Bearer ${token2}` },
    });
    expect(after2.statusCode).toBe(401);
  });

  it("changing password revokes API keys", async () => {
    const { username, password } = await createUser();
    const token = await loginAs(username, password);

    // Create an API key
    const createKeyRes = await testApp.app.inject({
      method: "POST",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${token}` },
      payload: { name: "test-key" },
    });
    expect(createKeyRes.statusCode).toBe(201);
    const apiKey = JSON.parse(createKeyRes.body).key;

    // Verify the key works (hit a public-ish endpoint that still reads auth)
    const keyCheck = await testApp.app.inject({
      method: "GET",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${apiKey}` },
    });
    expect(keyCheck.statusCode).toBe(200);

    // Change password
    const changeRes = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/change-password",
      headers: { authorization: `Bearer ${token}` },
      payload: { currentPassword: password, newPassword: "NewValid2" },
    });
    expect(changeRes.statusCode).toBe(200);

    // API key should now be revoked
    const keyAfter = await testApp.app.inject({
      method: "GET",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${apiKey}` },
    });
    expect(keyAfter.statusCode).toBe(401);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// PASSWORD RESET SIDE EFFECTS (admin resets another user)
// ═══════════════════════════════════════════════════════════════════════════
describe("Password reset side effects", () => {
  it("admin reset invalidates target user sessions", async () => {
    const { username, password, id } = await createUser();
    const userToken = await loginAs(username, password);

    // Verify user session works
    const before = await testApp.app.inject({
      method: "GET",
      url: "/api/auth/session",
      headers: { authorization: `Bearer ${userToken}` },
    });
    expect(before.statusCode).toBe(200);

    // Admin resets the user's password
    const resetRes = await testApp.app.inject({
      method: "POST",
      url: `/api/auth/users/${id}/reset-password`,
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { newPassword: "ResetPass1" },
    });
    expect(resetRes.statusCode).toBe(200);

    // User session should now be invalid
    const after = await testApp.app.inject({
      method: "GET",
      url: "/api/auth/session",
      headers: { authorization: `Bearer ${userToken}` },
    });
    expect(after.statusCode).toBe(401);
  });

  it("admin reset revokes target user API keys", async () => {
    const { username, password, id } = await createUser();
    const userToken = await loginAs(username, password);

    // Create an API key for the target user
    const createKeyRes = await testApp.app.inject({
      method: "POST",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${userToken}` },
      payload: { name: "target-key" },
    });
    expect(createKeyRes.statusCode).toBe(201);
    const apiKey = JSON.parse(createKeyRes.body).key;

    // Verify the key works
    const keyBefore = await testApp.app.inject({
      method: "GET",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${apiKey}` },
    });
    expect(keyBefore.statusCode).toBe(200);

    // Admin resets the user's password
    const resetRes = await testApp.app.inject({
      method: "POST",
      url: `/api/auth/users/${id}/reset-password`,
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { newPassword: "ResetPass2" },
    });
    expect(resetRes.statusCode).toBe(200);

    // API key should now be revoked
    const keyAfter = await testApp.app.inject({
      method: "GET",
      url: "/api/v1/api-keys",
      headers: { authorization: `Bearer ${apiKey}` },
    });
    expect(keyAfter.statusCode).toBe(401);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// REGISTER VALIDATION
// ═══════════════════════════════════════════════════════════════════════════
describe("Register validation", () => {
  it("invalid username chars returns 400", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/register",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { username: "bad user!@#", password: "ValidPass1" },
    });
    expect(res.statusCode).toBe(400);
    expect(JSON.parse(res.body).code).toBe("VALIDATION_ERROR");
  });

  it("username too short (2 chars) returns 400", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/register",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { username: "ab", password: "ValidPass1" },
    });
    expect(res.statusCode).toBe(400);
    expect(JSON.parse(res.body).code).toBe("VALIDATION_ERROR");
  });

  it("weak password returns 400", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/register",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { username: uid(), password: "weak" },
    });
    expect(res.statusCode).toBe(400);
    expect(JSON.parse(res.body).code).toBe("VALIDATION_ERROR");
  });

  it("non-existent team name returns 400", async () => {
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/register",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: {
        username: uid(),
        password: "ValidPass1",
        team: `ghost_team_${Date.now()}`,
      },
    });
    expect(res.statusCode).toBe(400);
    expect(JSON.parse(res.body).code).toBe("VALIDATION_ERROR");
  });

  it("unknown role defaults to user", async () => {
    const username = uid();
    const res = await testApp.app.inject({
      method: "POST",
      url: "/api/auth/register",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { username, password: "ValidPass1", role: "bogus" },
    });
    expect(res.statusCode).toBe(201);
    const body = JSON.parse(res.body);
    expect(body.role).toBe("user");
  });

  it("delete non-existent user returns 404", async () => {
    const res = await testApp.app.inject({
      method: "DELETE",
      url: "/api/auth/users/00000000-0000-0000-0000-000000000000",
      headers: { authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(404);
  });
});
