import request from "supertest";
import { afterAll, beforeAll, describe, expect, it } from "vitest";

import { createApp } from "../src/app.js";
import { runMigrations } from "../src/db/migrate.js";
import { pool } from "../src/db/pool.js";
import { seedDatabase } from "../src/db/seed.js";

const app = createApp();

describe("Rental API", () => {
  let token = "";

  beforeAll(async () => {
    await runMigrations();
    await seedDatabase({ reset: true });

    const loginResponse = await request(app).post("/api/auth/login").send({
      email: "admin@rental.local",
      password: "Admin12345!"
    });

    token = loginResponse.body.token;
  });

  afterAll(async () => {
    await pool.end();
  });

  it("logs in and returns a JWT session", async () => {
    const response = await request(app).post("/api/auth/login").send({
      email: "admin@rental.local",
      password: "Admin12345!"
    });

    expect(response.status).toBe(200);
    expect(response.body.token).toEqual(expect.any(String));
    expect(response.body.user.email).toBe("admin@rental.local");
  });

  it("returns timeline groups for an authenticated user", async () => {
    const response = await request(app)
      .get("/api/timeline?from=2026-04-01&to=2026-05-15")
      .set("Authorization", `Bearer ${token}`);

    expect(response.status).toBe(200);
    expect(Array.isArray(response.body.groups)).toBe(true);
    expect(response.body.range.dayCount).toBeGreaterThan(0);
  });

  it("stores equipment ordering preferences per authenticated user", async () => {
    const itemsResponse = await request(app)
      .get("/api/equipment-items?includeInactive=true")
      .set("Authorization", `Bearer ${token}`);
    const categoriesResponse = await request(app)
      .get("/api/equipment-categories")
      .set("Authorization", `Bearer ${token}`);

    const firstItemId = itemsResponse.body.data[0]?.id;
    const secondItemId = itemsResponse.body.data[1]?.id;
    const firstCategoryId = categoriesResponse.body.data[0]?.id;
    const secondCategoryId = categoriesResponse.body.data[1]?.id;

    expect(firstItemId).toEqual(expect.any(String));
    expect(secondItemId).toEqual(expect.any(String));
    expect(firstCategoryId).toEqual(expect.any(String));
    expect(secondCategoryId).toEqual(expect.any(String));

    const patchResponse = await request(app)
      .patch("/api/auth/preferences")
      .set("Authorization", `Bearer ${token}`)
      .send({
        equipmentCategoryOrder: [secondCategoryId, firstCategoryId],
        equipmentItemOrder: [secondItemId, firstItemId],
        timelineIncludeInactive: true,
        equipmentIncludeInactive: false
      });

    expect(patchResponse.status).toBe(200);
    expect(patchResponse.body.data.equipmentCategoryOrder).toEqual([secondCategoryId, firstCategoryId]);
    expect(patchResponse.body.data.equipmentItemOrder).toEqual([secondItemId, firstItemId]);
    expect(patchResponse.body.data.timelineIncludeInactive).toBe(true);
    expect(patchResponse.body.data.equipmentIncludeInactive).toBe(false);

    const getResponse = await request(app)
      .get("/api/auth/preferences")
      .set("Authorization", `Bearer ${token}`);

    expect(getResponse.status).toBe(200);
    expect(getResponse.body.data.equipmentCategoryOrder).toEqual([secondCategoryId, firstCategoryId]);
    expect(getResponse.body.data.equipmentItemOrder).toEqual([secondItemId, firstItemId]);
    expect(getResponse.body.data.timelineIncludeInactive).toBe(true);
    expect(getResponse.body.data.equipmentIncludeInactive).toBe(false);
  });

  it("blocks conflicting booking creation on the same equipment item", async () => {
    const itemsResponse = await request(app)
      .get("/api/equipment-items?search=L14-001&includeInactive=true")
      .set("Authorization", `Bearer ${token}`);

    const equipmentItemId = itemsResponse.body.data[0]?.id;

    expect(equipmentItemId).toEqual(expect.any(String));

    const response = await request(app)
      .post("/api/bookings")
      .set("Authorization", `Bearer ${token}`)
      .send({
        equipmentItemIds: [equipmentItemId],
        customerName: "Konflikt Test",
        startDate: "2026-04-19",
        endDate: "2026-04-23"
      });

    expect(response.status).toBe(409);
    expect(response.body.error).toMatch(/conflict/i);
  });

  it("blocks creating an identical booking for the same item and interval", async () => {
    const itemsResponse = await request(app)
      .get("/api/equipment-items?search=LEG-001&includeInactive=true")
      .set("Authorization", `Bearer ${token}`);

    const equipmentItemId = itemsResponse.body.data[0]?.id;

    expect(equipmentItemId).toEqual(expect.any(String));

    const response = await request(app)
      .post("/api/bookings")
      .set("Authorization", `Bearer ${token}`)
      .send({
        equipmentItemIds: [equipmentItemId],
        customerName: "Pixel Forge",
        projectName: "Turniej gamingowy",
        startDate: "2026-05-02",
        endDate: "2026-05-07"
      });

    expect(response.status).toBe(409);
    expect(response.body.error).toMatch(/identical booking/i);
  });

  it("generates an order number automatically when booking is saved without one", async () => {
    const itemsResponse = await request(app)
      .get("/api/equipment-items?search=L14-001&includeInactive=true")
      .set("Authorization", `Bearer ${token}`);

    const equipmentItemId = itemsResponse.body.data[0]?.id;

    expect(equipmentItemId).toEqual(expect.any(String));

    const createResponse = await request(app)
      .post("/api/bookings")
      .set("Authorization", `Bearer ${token}`)
      .send({
        equipmentItemIds: [equipmentItemId],
        customerName: "Auto Numer Test",
        startDate: "2026-06-10",
        endDate: "2026-06-12"
      });

    expect(createResponse.status).toBe(201);
    expect(createResponse.body.data[0]?.orderNumber).toMatch(/^ZAM-\d{8}-\d{4}$/);

    const bookingId = createResponse.body.data[0]?.id;

    expect(bookingId).toEqual(expect.any(String));

    const deleteResponse = await request(app)
      .delete(`/api/bookings/${bookingId}`)
      .set("Authorization", `Bearer ${token}`);

    expect(deleteResponse.status).toBe(204);
  });

  it("calculates total price from day rate when total price is not provided", async () => {
    const itemsResponse = await request(app)
      .get("/api/equipment-items?search=L15-001&includeInactive=true")
      .set("Authorization", `Bearer ${token}`);

    const equipmentItemId = itemsResponse.body.data[0]?.id;

    expect(equipmentItemId).toEqual(expect.any(String));

    const createResponse = await request(app)
      .post("/api/bookings")
      .set("Authorization", `Bearer ${token}`)
      .send({
        equipmentItemIds: [equipmentItemId],
        customerName: "Cennik Test",
        startDate: "2026-06-15",
        endDate: "2026-06-17",
        dayRate: 125
      });

    expect(createResponse.status).toBe(201);
    expect(createResponse.body.data[0]?.dayRate).toBe(125);
    expect(createResponse.body.data[0]?.totalPrice).toBe(375);

    const bookingId = createResponse.body.data[0]?.id;

    const deleteResponse = await request(app)
      .delete(`/api/bookings/${bookingId}`)
      .set("Authorization", `Bearer ${token}`);

    expect(deleteResponse.status).toBe(204);
  });

  it("expands one booking project scope onto additional equipment items", async () => {
    const itemsResponse = await request(app)
      .get("/api/equipment-items?search=HP%20ProBook%2015&includeInactive=true")
      .set("Authorization", `Bearer ${token}`);

    const firstItemId = itemsResponse.body.data[0]?.id;
    const secondItemId = itemsResponse.body.data[1]?.id;

    expect(firstItemId).toEqual(expect.any(String));
    expect(secondItemId).toEqual(expect.any(String));

    const createResponse = await request(app)
      .post("/api/bookings")
      .set("Authorization", `Bearer ${token}`)
      .send({
        equipmentItemIds: [firstItemId],
        customerName: "Projekt Scope Test",
        startDate: "2026-06-20",
        endDate: "2026-06-22"
      });

    expect(createResponse.status).toBe(201);
    expect(createResponse.body.data[0]?.projectNumber).toMatch(/^PRJ-\d{8}-\d{4}$/);

    const anchorBookingId = createResponse.body.data[0]?.id;

    expect(anchorBookingId).toEqual(expect.any(String));

    const scopeResponse = await request(app)
      .patch(`/api/bookings/${anchorBookingId}/scope`)
      .set("Authorization", `Bearer ${token}`)
      .send({
        equipmentItemIds: [firstItemId, secondItemId],
        startDate: "2026-06-21",
        endDate: "2026-06-24"
      });

    expect(scopeResponse.status).toBe(200);
    expect(scopeResponse.body.data).toHaveLength(2);
    expect(new Set(scopeResponse.body.data.map((booking: { projectNumber: string }) => booking.projectNumber)).size).toBe(1);
    expect(new Set(scopeResponse.body.data.map((booking: { equipmentItemId: string }) => booking.equipmentItemId))).toEqual(
      new Set([firstItemId, secondItemId])
    );
    expect(new Set(scopeResponse.body.data.map((booking: { startDate: string }) => booking.startDate))).toEqual(new Set(["2026-06-21"]));
    expect(new Set(scopeResponse.body.data.map((booking: { endDate: string }) => booking.endDate))).toEqual(new Set(["2026-06-24"]));

    await Promise.all(
      scopeResponse.body.data.map((booking: { id: string }) =>
        request(app)
          .delete(`/api/bookings/${booking.id}`)
          .set("Authorization", `Bearer ${token}`)
      )
    );
  });

  it("creates and updates a user with a 6-character password", async () => {
    const email = "shortpass@rental.local";

    const createResponse = await request(app)
      .post("/api/users")
      .set("Authorization", `Bearer ${token}`)
      .send({
        name: "Haslo Test",
        email,
        password: "Alicja",
        role: "viewer",
        isActive: true
      });

    expect(createResponse.status).toBe(201);
    expect(createResponse.body.data.email).toBe(email);

    const loginWithCreatedPassword = await request(app).post("/api/auth/login").send({
      email,
      password: "Alicja"
    });

    expect(loginWithCreatedPassword.status).toBe(200);

    const updateResponse = await request(app)
      .patch(`/api/users/${createResponse.body.data.id}`)
      .set("Authorization", `Bearer ${token}`)
      .send({
        password: "Mirek1"
      });

    expect(updateResponse.status).toBe(200);

    const loginWithUpdatedPassword = await request(app).post("/api/auth/login").send({
      email,
      password: "Mirek1"
    });

    expect(loginWithUpdatedPassword.status).toBe(200);
  });
});
