/**
 * Integration tests for the Teams API routes.
 *
 * Uses the same test server infrastructure as api.test.ts — a real Fastify
 * app backed by an isolated temp SQLite DB, exercised via `app.inject()`.
 */

import { randomUUID } from "node:crypto";
import { eq, sql } from "drizzle-orm";
import { afterAll, beforeAll, beforeEach, 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 app: TestApp["app"];
let adminToken: string;

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

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

// Helper: seed a Default team if not present
function ensureDefaultTeam(): string {
  const existing = db.select().from(schema.teams).where(eq(schema.teams.name, "Default")).get();
  if (existing) return existing.id;
  const id = randomUUID();
  db.insert(schema.teams).values({ id, name: "Default" }).run();
  return id;
}

// Helper: clean all teams except Default, and recreate Default if missing
function resetTeams(): string {
  // Delete non-Default teams
  db.delete(schema.teams).where(sql`${schema.teams.name} != 'Default'`).run();
  return ensureDefaultTeam();
}

// ═══════════════════════════════════════════════════════════════════════════
// GET /api/v1/teams
// ═══════════════════════════════════════════════════════════════════════════
describe("GET /api/v1/teams", () => {
  beforeEach(() => resetTeams());

  it("returns teams with member counts", async () => {
    const res = await app.inject({
      method: "GET",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body);
    expect(body.teams).toBeDefined();
    expect(Array.isArray(body.teams)).toBe(true);
    // Default team should exist
    const defaultTeam = body.teams.find((t: { name: string }) => t.name === "Default");
    expect(defaultTeam).toBeDefined();
    expect(typeof defaultTeam.memberCount).toBe("number");
    expect(typeof defaultTeam.createdAt).toBe("string");
    expect(defaultTeam.id).toBeDefined();
  });

  it("requires authentication", async () => {
    const res = await app.inject({
      method: "GET",
      url: "/api/v1/teams",
    });
    expect(res.statusCode).toBe(401);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// POST /api/v1/teams
// ═══════════════════════════════════════════════════════════════════════════
describe("POST /api/v1/teams", () => {
  beforeEach(() => resetTeams());

  it("creates a team", async () => {
    const res = await app.inject({
      method: "POST",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "Engineering" },
    });
    expect(res.statusCode).toBe(201);
    const body = JSON.parse(res.body);
    expect(body.id).toBeDefined();
    expect(body.name).toBe("Engineering");
  });

  it("requires admin", async () => {
    // Register a non-admin user
    const regRes = await app.inject({
      method: "POST",
      url: "/api/auth/register",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { username: "teamuser1", password: "TestPass1", role: "user" },
    });
    expect(regRes.statusCode).toBe(201);

    // Login as non-admin
    // First clear mustChangePassword
    const userId = JSON.parse(regRes.body).id;
    db.update(schema.users)
      .set({ mustChangePassword: false })
      .where(eq(schema.users.id, userId))
      .run();

    const loginRes = await app.inject({
      method: "POST",
      url: "/api/auth/login",
      payload: { username: "teamuser1", password: "TestPass1" },
    });
    const userToken = JSON.parse(loginRes.body).token;

    const res = await app.inject({
      method: "POST",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${userToken}` },
      payload: { name: "ShouldFail" },
    });
    expect(res.statusCode).toBe(403);

    // Cleanup
    db.delete(schema.users).where(eq(schema.users.id, userId)).run();
  });

  it("rejects duplicate names (case-insensitive)", async () => {
    // First create
    const res1 = await app.inject({
      method: "POST",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "Marketing" },
    });
    expect(res1.statusCode).toBe(201);

    // Duplicate with different case
    const res2 = await app.inject({
      method: "POST",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "MARKETING" },
    });
    expect(res2.statusCode).toBe(409);
    expect(JSON.parse(res2.body).code).toBe("CONFLICT");
  });

  it("rejects empty name", async () => {
    const res = await app.inject({
      method: "POST",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "" },
    });
    expect(res.statusCode).toBe(400);
    expect(JSON.parse(res.body).code).toBe("VALIDATION_ERROR");
  });

  it("rejects whitespace-only name", async () => {
    const res = await app.inject({
      method: "POST",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "   " },
    });
    expect(res.statusCode).toBe(400);
  });

  it("rejects name longer than 50 characters", async () => {
    const res = await app.inject({
      method: "POST",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "A".repeat(51) },
    });
    expect(res.statusCode).toBe(400);
    expect(JSON.parse(res.body).code).toBe("VALIDATION_ERROR");
  });

  it("trims whitespace from name", async () => {
    const res = await app.inject({
      method: "POST",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "  Sales  " },
    });
    expect(res.statusCode).toBe(201);
    expect(JSON.parse(res.body).name).toBe("Sales");
  });

  it("rejects missing name field", async () => {
    const res = await app.inject({
      method: "POST",
      url: "/api/v1/teams",
      headers: { authorization: `Bearer ${adminToken}` },
      payload: {},
    });
    expect(res.statusCode).toBe(400);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// PUT /api/v1/teams/:id
// ═══════════════════════════════════════════════════════════════════════════
describe("PUT /api/v1/teams/:id", () => {
  let teamId: string;

  beforeEach(() => {
    resetTeams();
    // Create a team to rename
    teamId = randomUUID();
    db.insert(schema.teams).values({ id: teamId, name: "OldName" }).run();
  });

  it("renames a team", async () => {
    const res = await app.inject({
      method: "PUT",
      url: `/api/v1/teams/${teamId}`,
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "NewName" },
    });
    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body).ok).toBe(true);

    // Verify
    const team = db.select().from(schema.teams).where(eq(schema.teams.id, teamId)).get();
    expect(team?.name).toBe("NewName");
  });

  it("requires admin", async () => {
    const res = await app.inject({
      method: "PUT",
      url: `/api/v1/teams/${teamId}`,
      payload: { name: "Hacked" },
    });
    expect(res.statusCode).toBe(401);
  });

  it("rejects duplicate names", async () => {
    // Try to rename to "Default" which already exists
    const res = await app.inject({
      method: "PUT",
      url: `/api/v1/teams/${teamId}`,
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "Default" },
    });
    expect(res.statusCode).toBe(409);
    expect(JSON.parse(res.body).code).toBe("CONFLICT");
  });

  it("allows renaming to same name (case-exact)", async () => {
    const res = await app.inject({
      method: "PUT",
      url: `/api/v1/teams/${teamId}`,
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "OldName" },
    });
    // Should succeed — same team, same name
    expect(res.statusCode).toBe(200);
  });

  it("returns 404 for non-existent team", async () => {
    const res = await app.inject({
      method: "PUT",
      url: `/api/v1/teams/${randomUUID()}`,
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "Whatever" },
    });
    expect(res.statusCode).toBe(404);
    expect(JSON.parse(res.body).code).toBe("NOT_FOUND");
  });

  it("rejects empty name", async () => {
    const res = await app.inject({
      method: "PUT",
      url: `/api/v1/teams/${teamId}`,
      headers: { authorization: `Bearer ${adminToken}` },
      payload: { name: "" },
    });
    expect(res.statusCode).toBe(400);
  });
});

// ═══════════════════════════════════════════════════════════════════════════
// DELETE /api/v1/teams/:id
// ═══════════════════════════════════════════════════════════════════════════
describe("DELETE /api/v1/teams/:id", () => {
  let defaultTeamId: string;

  beforeEach(() => {
    defaultTeamId = resetTeams();
  });

  it("deletes an empty team", async () => {
    const teamId = randomUUID();
    db.insert(schema.teams).values({ id: teamId, name: "ToDelete" }).run();

    const res = await app.inject({
      method: "DELETE",
      url: `/api/v1/teams/${teamId}`,
      headers: { authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body).ok).toBe(true);

    // Verify it's gone
    const team = db.select().from(schema.teams).where(eq(schema.teams.id, teamId)).get();
    expect(team).toBeUndefined();
  });

  it("rejects deleting a team with members", async () => {
    const teamId = randomUUID();
    db.insert(schema.teams).values({ id: teamId, name: "HasMembers" }).run();

    // Assign a user to this team
    const userId = randomUUID();
    db.insert(schema.users)
      .values({
        id: userId,
        username: "memberuser",
        passwordHash: "dummy:hash",
        role: "user",
        team: teamId,
      })
      .run();

    const res = await app.inject({
      method: "DELETE",
      url: `/api/v1/teams/${teamId}`,
      headers: { authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(400);
    expect(JSON.parse(res.body).error).toMatch(/members/i);

    // Cleanup
    db.delete(schema.users).where(eq(schema.users.id, userId)).run();
    db.delete(schema.teams).where(eq(schema.teams.id, teamId)).run();
  });

  it("rejects deleting the Default team", async () => {
    const res = await app.inject({
      method: "DELETE",
      url: `/api/v1/teams/${defaultTeamId}`,
      headers: { authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(400);
    expect(JSON.parse(res.body).error).toMatch(/default/i);
  });

  it("requires admin", async () => {
    const teamId = randomUUID();
    db.insert(schema.teams).values({ id: teamId, name: "NoAuth" }).run();

    const res = await app.inject({
      method: "DELETE",
      url: `/api/v1/teams/${teamId}`,
    });
    expect(res.statusCode).toBe(401);

    // Cleanup
    db.delete(schema.teams).where(eq(schema.teams.id, teamId)).run();
  });

  it("returns 404 for non-existent team", async () => {
    const res = await app.inject({
      method: "DELETE",
      url: `/api/v1/teams/${randomUUID()}`,
      headers: { authorization: `Bearer ${adminToken}` },
    });
    expect(res.statusCode).toBe(404);
    expect(JSON.parse(res.body).code).toBe("NOT_FOUND");
  });
});
