/**
 * Tests for /medications API endpoints.
 * Tests CRUD operations for medications.
 */
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
import {
  buildTestApp,
  closeTestApp,
  clearTestData,
  createTestUser,
  createTestMedication,
  TestContext,
} from "./setup.js";

// =============================================================================
// Route Registration
// =============================================================================

async function registerMedicationRoutes(ctx: TestContext) {
  const { app, client } = ctx;

  // GET /medications - List all medications
  app.get("/medications", async (request, reply) => {
    const userId = 1;

    const result = await client.execute({
      sql: `SELECT * FROM medications WHERE user_id = ? ORDER BY name`,
      args: [userId],
    });

    return result.rows.map((m) => ({
      id: m.id,
      name: m.name,
      genericName: m.generic_name,
      takenBy: JSON.parse((m.taken_by_json as string) || "[]"),
      packCount: m.pack_count,
      blistersPerPack: m.blisters_per_pack,
      pillsPerBlister: m.pills_per_blister,
      looseTablets: m.loose_tablets,
      pillWeightMg: m.pill_weight_mg,
      imageUrl: m.image_url,
      expiryDate: m.expiry_date,
      notes: m.notes,
      intakeRemindersEnabled: Boolean(m.intake_reminders_enabled),
      blisters: (() => {
        const usage: number[] = JSON.parse((m.usage_json as string) || "[]");
        const every: number[] = JSON.parse((m.every_json as string) || "[]");
        const start: string[] = JSON.parse((m.start_json as string) || "[]");
        return usage.map((u, i) => ({
          usage: u,
          every: every[i] || 1,
          start: start[i] || new Date().toISOString(),
        }));
      })(),
    }));
  });

  // POST /medications - Create medication
  app.post<{
    Body: {
      name: string;
      genericName?: string;
      takenBy?: string[];
      packCount?: number;
      blistersPerPack?: number;
      pillsPerBlister?: number;
      looseTablets?: number;
      pillWeightMg?: number;
      expiryDate?: string;
      notes?: string;
      intakeRemindersEnabled?: boolean;
      blisters: Array<{ usage: number; every: number; start: string }>;
    };
  }>("/medications", async (request, reply) => {
    const userId = 1;
    const body = request.body || {};

    // Validation
    if (!body.name || body.name.length === 0) {
      return reply.status(400).send({ error: "Name is required" });
    }
    if (body.name.length > 100) {
      return reply.status(400).send({ error: "Name must be 100 characters or less" });
    }
    if (!body.blisters || body.blisters.length === 0) {
      return reply.status(400).send({ error: "At least one intake schedule is required" });
    }
    if (body.blisters.length > 12) {
      return reply.status(400).send({ error: "Maximum 12 intake schedules allowed" });
    }

    const usageJson = JSON.stringify(body.blisters.map((b) => b.usage));
    const everyJson = JSON.stringify(body.blisters.map((b) => b.every));
    const startJson = JSON.stringify(body.blisters.map((b) => b.start));
    const takenByJson = JSON.stringify(body.takenBy || []);

    const result = await client.execute({
      sql: `INSERT INTO medications (
        user_id, name, generic_name, taken_by_json,
        pack_count, blisters_per_pack, pills_per_blister, loose_tablets,
        pill_weight_mg, expiry_date, notes, intake_reminders_enabled,
        usage_json, every_json, start_json
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id`,
      args: [
        userId,
        body.name,
        body.genericName || null,
        takenByJson,
        body.packCount ?? 1,
        body.blistersPerPack ?? 1,
        body.pillsPerBlister ?? 1,
        body.looseTablets ?? 0,
        body.pillWeightMg ?? null,
        body.expiryDate || null,
        body.notes || null,
        body.intakeRemindersEnabled ? 1 : 0,
        usageJson,
        everyJson,
        startJson,
      ],
    });

    return { id: result.rows[0].id, success: true };
  });

  // PUT /medications/:id - Update medication
  app.put<{
    Params: { id: string };
    Body: {
      name: string;
      genericName?: string;
      takenBy?: string[];
      packCount?: number;
      blistersPerPack?: number;
      pillsPerBlister?: number;
      looseTablets?: number;
      pillWeightMg?: number;
      expiryDate?: string;
      notes?: string;
      intakeRemindersEnabled?: boolean;
      blisters: Array<{ usage: number; every: number; start: string }>;
    };
  }>("/medications/:id", async (request, reply) => {
    const userId = 1;
    const medId = parseInt(request.params.id, 10);
    const body = request.body || {};

    // Check ownership
    const existing = await client.execute({
      sql: `SELECT id FROM medications WHERE id = ? AND user_id = ?`,
      args: [medId, userId],
    });

    if (existing.rows.length === 0) {
      return reply.status(404).send({ error: "Medication not found" });
    }

    // Validation
    if (!body.name || body.name.length === 0) {
      return reply.status(400).send({ error: "Name is required" });
    }
    if (!body.blisters || body.blisters.length === 0) {
      return reply.status(400).send({ error: "At least one intake schedule is required" });
    }

    const usageJson = JSON.stringify(body.blisters.map((b) => b.usage));
    const everyJson = JSON.stringify(body.blisters.map((b) => b.every));
    const startJson = JSON.stringify(body.blisters.map((b) => b.start));
    const takenByJson = JSON.stringify(body.takenBy || []);

    await client.execute({
      sql: `UPDATE medications SET
        name = ?, generic_name = ?, taken_by_json = ?,
        pack_count = ?, blisters_per_pack = ?, pills_per_blister = ?, loose_tablets = ?,
        pill_weight_mg = ?, expiry_date = ?, notes = ?, intake_reminders_enabled = ?,
        usage_json = ?, every_json = ?, start_json = ?,
        updated_at = strftime('%s','now')
      WHERE id = ? AND user_id = ?`,
      args: [
        body.name,
        body.genericName || null,
        takenByJson,
        body.packCount ?? 1,
        body.blistersPerPack ?? 1,
        body.pillsPerBlister ?? 1,
        body.looseTablets ?? 0,
        body.pillWeightMg ?? null,
        body.expiryDate || null,
        body.notes || null,
        body.intakeRemindersEnabled ? 1 : 0,
        usageJson,
        everyJson,
        startJson,
        medId,
        userId,
      ],
    });

    return { success: true };
  });

  // DELETE /medications/:id - Delete medication
  app.delete<{ Params: { id: string } }>("/medications/:id", async (request, reply) => {
    const userId = 1;
    const medId = parseInt(request.params.id, 10);

    // Check ownership
    const existing = await client.execute({
      sql: `SELECT id FROM medications WHERE id = ? AND user_id = ?`,
      args: [medId, userId],
    });

    if (existing.rows.length === 0) {
      return reply.status(404).send({ error: "Medication not found" });
    }

    await client.execute({
      sql: `DELETE FROM medications WHERE id = ? AND user_id = ?`,
      args: [medId, userId],
    });

    return { success: true };
  });

  // GET /medications/:id - Get single medication
  app.get<{ Params: { id: string } }>("/medications/:id", async (request, reply) => {
    const userId = 1;
    const medId = parseInt(request.params.id, 10);

    const result = await client.execute({
      sql: `SELECT * FROM medications WHERE id = ? AND user_id = ?`,
      args: [medId, userId],
    });

    if (result.rows.length === 0) {
      return reply.status(404).send({ error: "Medication not found" });
    }

    const m = result.rows[0];
    return {
      id: m.id,
      name: m.name,
      genericName: m.generic_name,
      takenBy: JSON.parse((m.taken_by_json as string) || "[]"),
      packCount: m.pack_count,
      blistersPerPack: m.blisters_per_pack,
      pillsPerBlister: m.pills_per_blister,
      looseTablets: m.loose_tablets,
      pillWeightMg: m.pill_weight_mg,
      imageUrl: m.image_url,
      expiryDate: m.expiry_date,
      notes: m.notes,
      intakeRemindersEnabled: Boolean(m.intake_reminders_enabled),
      blisters: (() => {
        const usage: number[] = JSON.parse((m.usage_json as string) || "[]");
        const every: number[] = JSON.parse((m.every_json as string) || "[]");
        const start: string[] = JSON.parse((m.start_json as string) || "[]");
        return usage.map((u, i) => ({
          usage: u,
          every: every[i] || 1,
          start: start[i] || new Date().toISOString(),
        }));
      })(),
    };
  });
}

// =============================================================================
// Tests
// =============================================================================

describe("Medications API", () => {
  let ctx: TestContext;
  let userId: number;

  beforeAll(async () => {
    ctx = await buildTestApp();
    await registerMedicationRoutes(ctx);
    await ctx.app.ready();
  });

  afterAll(async () => {
    await closeTestApp(ctx);
  });

  beforeEach(async () => {
    await clearTestData(ctx.client);
    await ctx.client.execute("DELETE FROM sqlite_sequence WHERE name='users'");
    await ctx.client.execute("DELETE FROM sqlite_sequence WHERE name='medications'");
    userId = await createTestUser(ctx.client, { username: "testuser" });
  });

  // ---------------------------------------------------------------------------
  // GET /medications
  // ---------------------------------------------------------------------------

  describe("GET /medications", () => {
    it("should return empty array when no medications", async () => {
      const response = await ctx.app.inject({
        method: "GET",
        url: "/medications",
      });

      expect(response.statusCode).toBe(200);
      expect(response.json()).toEqual([]);
    });

    it("should return list of medications", async () => {
      await createTestMedication(ctx.client, {
        userId,
        name: "Aspirin",
        genericName: "Acetylsalicylic acid",
        takenBy: ["Daniel"],
        packCount: 2,
        pillsPerBlister: 10,
      });
      await createTestMedication(ctx.client, {
        userId,
        name: "Ibuprofen",
      });

      const response = await ctx.app.inject({
        method: "GET",
        url: "/medications",
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data).toHaveLength(2);
      // Sorted by name
      expect(data[0].name).toBe("Aspirin");
      expect(data[0].genericName).toBe("Acetylsalicylic acid");
      expect(data[0].takenBy).toEqual(["Daniel"]);
      expect(data[1].name).toBe("Ibuprofen");
    });

    it("should return medication with all fields", async () => {
      const startDate = "2025-01-01T08:00:00.000Z";
      await createTestMedication(ctx.client, {
        userId,
        name: "Test Med",
        genericName: "Generic Name",
        takenBy: ["Person1", "Person2"],
        packCount: 3,
        blistersPerPack: 2,
        pillsPerBlister: 14,
        looseTablets: 5,
        pillWeightMg: 500,
        blisters: [
          { usage: 1, every: 1, start: startDate },
          { usage: 2, every: 2, start: startDate },
        ],
      });

      const response = await ctx.app.inject({
        method: "GET",
        url: "/medications",
      });

      expect(response.statusCode).toBe(200);
      const [med] = response.json();
      expect(med.name).toBe("Test Med");
      expect(med.genericName).toBe("Generic Name");
      expect(med.takenBy).toEqual(["Person1", "Person2"]);
      expect(med.packCount).toBe(3);
      expect(med.blistersPerPack).toBe(2);
      expect(med.pillsPerBlister).toBe(14);
      expect(med.looseTablets).toBe(5);
      expect(med.pillWeightMg).toBe(500);
      expect(med.blisters).toHaveLength(2);
      expect(med.blisters[0]).toEqual({ usage: 1, every: 1, start: startDate });
      expect(med.blisters[1]).toEqual({ usage: 2, every: 2, start: startDate });
    });
  });

  // ---------------------------------------------------------------------------
  // POST /medications
  // ---------------------------------------------------------------------------

  describe("POST /medications", () => {
    it("should create a medication", async () => {
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications",
        payload: {
          name: "New Med",
          blisters: [{ usage: 1, every: 1, start: "2025-01-01T08:00:00.000Z" }],
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data.success).toBe(true);
      expect(data.id).toBeDefined();

      // Verify in database
      const result = await ctx.client.execute({
        sql: `SELECT name FROM medications WHERE id = ?`,
        args: [data.id],
      });
      expect(result.rows[0].name).toBe("New Med");
    });

    it("should create medication with all fields", async () => {
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications",
        payload: {
          name: "Full Med",
          genericName: "Generic",
          takenBy: ["Alice", "Bob"],
          packCount: 2,
          blistersPerPack: 3,
          pillsPerBlister: 10,
          looseTablets: 5,
          pillWeightMg: 250,
          expiryDate: "2026-12-31",
          notes: "Take with food",
          intakeRemindersEnabled: true,
          blisters: [
            { usage: 1, every: 1, start: "2025-01-01T08:00:00.000Z" },
            { usage: 2, every: 1, start: "2025-01-01T20:00:00.000Z" },
          ],
        },
      });

      expect(response.statusCode).toBe(200);

      // Verify
      const medId = response.json().id;
      const result = await ctx.client.execute({
        sql: `SELECT * FROM medications WHERE id = ?`,
        args: [medId],
      });
      const med = result.rows[0];
      expect(med.name).toBe("Full Med");
      expect(med.generic_name).toBe("Generic");
      expect(JSON.parse(med.taken_by_json as string)).toEqual(["Alice", "Bob"]);
      expect(med.pack_count).toBe(2);
      expect(med.blisters_per_pack).toBe(3);
      expect(med.pills_per_blister).toBe(10);
      expect(med.loose_tablets).toBe(5);
      expect(med.pill_weight_mg).toBe(250);
      expect(med.expiry_date).toBe("2026-12-31");
      expect(med.notes).toBe("Take with food");
      expect(med.intake_reminders_enabled).toBe(1);
    });

    it("should reject request without name", async () => {
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications",
        payload: {
          blisters: [{ usage: 1, every: 1, start: "2025-01-01T08:00:00.000Z" }],
        },
      });

      expect(response.statusCode).toBe(400);
      expect(response.json().error).toBe("Name is required");
    });

    it("should reject request without blisters", async () => {
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications",
        payload: {
          name: "Test",
          blisters: [],
        },
      });

      expect(response.statusCode).toBe(400);
      expect(response.json().error).toBe("At least one intake schedule is required");
    });

    it("should reject name over 100 characters", async () => {
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications",
        payload: {
          name: "A".repeat(101),
          blisters: [{ usage: 1, every: 1, start: "2025-01-01T08:00:00.000Z" }],
        },
      });

      expect(response.statusCode).toBe(400);
      expect(response.json().error).toBe("Name must be 100 characters or less");
    });

    it("should reject more than 12 blisters", async () => {
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications",
        payload: {
          name: "Test",
          blisters: Array(13).fill({ usage: 1, every: 1, start: "2025-01-01T08:00:00.000Z" }),
        },
      });

      expect(response.statusCode).toBe(400);
      expect(response.json().error).toBe("Maximum 12 intake schedules allowed");
    });
  });

  // ---------------------------------------------------------------------------
  // PUT /medications/:id
  // ---------------------------------------------------------------------------

  describe("PUT /medications/:id", () => {
    it("should update a medication", async () => {
      const medId = await createTestMedication(ctx.client, {
        userId,
        name: "Old Name",
      });

      const response = await ctx.app.inject({
        method: "PUT",
        url: `/medications/${medId}`,
        payload: {
          name: "New Name",
          blisters: [{ usage: 2, every: 1, start: "2025-01-01T08:00:00.000Z" }],
        },
      });

      expect(response.statusCode).toBe(200);
      expect(response.json()).toEqual({ success: true });

      // Verify
      const result = await ctx.client.execute({
        sql: `SELECT name, usage_json FROM medications WHERE id = ?`,
        args: [medId],
      });
      expect(result.rows[0].name).toBe("New Name");
      expect(JSON.parse(result.rows[0].usage_json as string)).toEqual([2]);
    });

    it("should return 404 for non-existent medication", async () => {
      const response = await ctx.app.inject({
        method: "PUT",
        url: "/medications/99999",
        payload: {
          name: "Test",
          blisters: [{ usage: 1, every: 1, start: "2025-01-01T08:00:00.000Z" }],
        },
      });

      expect(response.statusCode).toBe(404);
      expect(response.json().error).toBe("Medication not found");
    });

    it("should not update medication of another user", async () => {
      // Create another user
      const otherUserId = await createTestUser(ctx.client, { username: "other" });
      const medId = await createTestMedication(ctx.client, {
        userId: otherUserId,
        name: "Other Med",
      });

      const response = await ctx.app.inject({
        method: "PUT",
        url: `/medications/${medId}`,
        payload: {
          name: "Hacked",
          blisters: [{ usage: 1, every: 1, start: "2025-01-01T08:00:00.000Z" }],
        },
      });

      expect(response.statusCode).toBe(404);
    });
  });

  // ---------------------------------------------------------------------------
  // DELETE /medications/:id
  // ---------------------------------------------------------------------------

  describe("DELETE /medications/:id", () => {
    it("should delete a medication", async () => {
      const medId = await createTestMedication(ctx.client, {
        userId,
        name: "To Delete",
      });

      const response = await ctx.app.inject({
        method: "DELETE",
        url: `/medications/${medId}`,
      });

      expect(response.statusCode).toBe(200);
      expect(response.json()).toEqual({ success: true });

      // Verify deleted
      const result = await ctx.client.execute({
        sql: `SELECT COUNT(*) as count FROM medications WHERE id = ?`,
        args: [medId],
      });
      expect(result.rows[0].count).toBe(0);
    });

    it("should return 404 for non-existent medication", async () => {
      const response = await ctx.app.inject({
        method: "DELETE",
        url: "/medications/99999",
      });

      expect(response.statusCode).toBe(404);
    });
  });

  // ---------------------------------------------------------------------------
  // GET /medications/:id
  // ---------------------------------------------------------------------------

  describe("GET /medications/:id", () => {
    it("should return single medication", async () => {
      const medId = await createTestMedication(ctx.client, {
        userId,
        name: "Single Med",
        genericName: "Generic",
        takenBy: ["Daniel"],
      });

      const response = await ctx.app.inject({
        method: "GET",
        url: `/medications/${medId}`,
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data.id).toBe(medId);
      expect(data.name).toBe("Single Med");
      expect(data.genericName).toBe("Generic");
      expect(data.takenBy).toEqual(["Daniel"]);
    });

    it("should return 404 for non-existent medication", async () => {
      const response = await ctx.app.inject({
        method: "GET",
        url: "/medications/99999",
      });

      expect(response.statusCode).toBe(404);
    });
  });

  // ---------------------------------------------------------------------------
  // Stock Calculation Tests
  // ---------------------------------------------------------------------------

  describe("Stock Calculation", () => {
    it("should calculate total pills correctly", async () => {
      await createTestMedication(ctx.client, {
        userId,
        name: "Stock Test",
        packCount: 2,
        blistersPerPack: 3,
        pillsPerBlister: 10,
        looseTablets: 5,
      });

      const response = await ctx.app.inject({
        method: "GET",
        url: "/medications",
      });

      const [med] = response.json();
      // Total = (2 packs × 3 blisters × 10 pills) + 5 loose = 65
      const totalPills =
        med.packCount * med.blistersPerPack * med.pillsPerBlister + med.looseTablets;
      expect(totalPills).toBe(65);
    });
  });
});
