/**
 * Tests for stock calculation modes (automatic vs manual).
 * Tests the /medications/usage endpoint with different settings.
 */
import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
import {
  buildTestApp,
  closeTestApp,
  clearTestData,
  createTestUser,
  createTestMedication,
  createTestDoseTracking,
  setUserSettings,
  TestContext,
} from "./setup.js";

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

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

  // POST /medications/usage - Calculate medication usage for a date range
  app.post<{ Body: { startDate: string; endDate: string } }>(
    "/medications/usage",
    async (request, reply) => {
      const userId = 1;
      const { startDate, endDate } = request.body || {};

      if (!startDate || !endDate) {
        return reply.status(400).send({ error: "startDate and endDate are required" });
      }

      const start = new Date(startDate);
      const end = new Date(endDate);

      // Get user settings
      const settingsResult = await client.execute({
        sql: `SELECT stock_calculation_mode FROM user_settings WHERE user_id = ?`,
        args: [userId],
      });
      const stockMode =
        settingsResult.rows.length > 0
          ? (settingsResult.rows[0].stock_calculation_mode as string)
          : "automatic";

      // Get all medications
      const medsResult = await client.execute({
        sql: `SELECT * FROM medications WHERE user_id = ?`,
        args: [userId],
      });

      const results = [];

      for (const med of medsResult.rows) {
        const totalPills =
          (med.pack_count as number) *
            (med.blisters_per_pack as number) *
            (med.pills_per_blister as number) +
          (med.loose_tablets as number);

        const blisterSize = med.pills_per_blister as number;

        // Calculate usage based on schedule
        const usageArr: number[] = JSON.parse((med.usage_json as string) || "[]");
        const everyArr: number[] = JSON.parse((med.every_json as string) || "[]");
        const startArr: string[] = JSON.parse((med.start_json as string) || "[]");

        let plannerUsage = 0;

        if (stockMode === "automatic") {
          // Automatic: Calculate from schedule
          for (let i = 0; i < usageArr.length; i++) {
            const usage = usageArr[i] || 0;
            const every = everyArr[i] || 1;
            const scheduleStart = new Date(startArr[i] || start);

            // Count doses from scheduleStart to end within the range
            let current = new Date(scheduleStart);
            while (current <= end) {
              if (current >= start) {
                plannerUsage += usage;
              }
              current.setDate(current.getDate() + every);
            }
          }
        } else {
          // Manual: Count only tracked doses in the date range
          const dosesResult = await client.execute({
            sql: `SELECT dose_id FROM dose_tracking 
                  WHERE user_id = ? 
                  AND taken_at >= ? 
                  AND taken_at <= ?`,
            args: [
              userId,
              Math.floor(start.getTime() / 1000),
              Math.floor(end.getTime() / 1000),
            ],
          });

          // Filter to doses for this medication
          const medIdStr = `${med.id}-`;
          for (const dose of dosesResult.rows) {
            const doseId = dose.dose_id as string;
            if (doseId.startsWith(medIdStr)) {
              // Parse usage from the schedule based on blister index
              const parts = doseId.split("-");
              if (parts.length >= 3) {
                const blisterIdx = parseInt(parts[1], 10);
                plannerUsage += usageArr[blisterIdx] || 1;
              }
            }
          }
        }

        // Calculate how many blisters/pills needed
        const blistersNeeded = Math.ceil(plannerUsage / blisterSize);
        const fullBlisters = Math.floor(plannerUsage / blisterSize);
        const loosePills = plannerUsage % blisterSize;

        results.push({
          medicationId: med.id,
          medicationName: med.name,
          totalPills,
          plannerUsage,
          blisterSize,
          blistersNeeded,
          fullBlisters,
          loosePills,
          enough: totalPills >= plannerUsage,
        });
      }

      return results;
    }
  );

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

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

    return result.rows.map((m) => ({
      id: m.id,
      name: m.name,
      packCount: m.pack_count,
      blistersPerPack: m.blisters_per_pack,
      pillsPerBlister: m.pills_per_blister,
      looseTablets: m.loose_tablets,
      totalPills:
        (m.pack_count as number) *
          (m.blisters_per_pack as number) *
          (m.pills_per_blister as number) +
        (m.loose_tablets as number),
    }));
  });
}

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

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

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

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

  beforeEach(async () => {
    await clearTestData(ctx.client);
    // Reset SQLite autoincrement so user gets ID 1
    await ctx.client.execute("DELETE FROM sqlite_sequence WHERE name='users'");
    userId = await createTestUser(ctx.client, { username: "testuser" });
  });

  // ---------------------------------------------------------------------------
  // Automatic Mode Tests
  // ---------------------------------------------------------------------------

  describe("Automatic mode", () => {
    beforeEach(async () => {
      await setUserSettings(ctx.client, {
        userId,
        stockCalculationMode: "automatic",
      });
    });

    it("should calculate usage from schedule", async () => {
      // Medication: 1 pill daily starting Jan 1
      const start = new Date("2025-01-01T00:00:00.000Z");
      await createTestMedication(ctx.client, {
        userId,
        name: "Aspirin",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 30,
        blisters: [{ usage: 1, every: 1, start: start.toISOString() }],
      });

      // Calculate usage for 10 days (Jan 1-10)
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data).toHaveLength(1);

      const med = data[0];
      expect(med.medicationName).toBe("Aspirin");
      expect(med.totalPills).toBe(30);
      expect(med.plannerUsage).toBe(10); // 10 days, 1 pill/day
      expect(med.enough).toBe(true);
    });

    it("should handle every-other-day schedules", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");
      await createTestMedication(ctx.client, {
        userId,
        name: "Med B",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 20,
        blisters: [{ usage: 2, every: 2, start: start.toISOString() }], // 2 pills every 2 days
      });

      // 10 days: Jan 1, 3, 5, 7, 9 = 5 doses × 2 pills = 10 pills
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data[0].plannerUsage).toBe(10);
    });

    it("should handle multiple blisters (schedules)", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");
      await createTestMedication(ctx.client, {
        userId,
        name: "Multi Schedule",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 50,
        blisters: [
          { usage: 1, every: 1, start: start.toISOString() }, // Morning: 1/day
          { usage: 1, every: 1, start: start.toISOString() }, // Evening: 1/day
        ],
      });

      // 10 days: 2 schedules × 10 days × 1 pill = 20 pills
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data[0].plannerUsage).toBe(20);
    });

    it("should return enough=false when stock insufficient", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");
      await createTestMedication(ctx.client, {
        userId,
        name: "Low Stock Med",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 5, // Only 5 pills
        blisters: [{ usage: 1, every: 1, start: start.toISOString() }],
      });

      // Need 10 pills but only have 5
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data[0].totalPills).toBe(5);
      expect(data[0].plannerUsage).toBe(10);
      expect(data[0].enough).toBe(false);
    });

    it("should calculate blister counts correctly", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");
      await createTestMedication(ctx.client, {
        userId,
        name: "Blister Test",
        packCount: 2,
        blistersPerPack: 2,
        pillsPerBlister: 10, // 4 blisters × 10 = 40 pills
        blisters: [{ usage: 1, every: 1, start: start.toISOString() }],
      });

      // 25 days = 25 pills needed = 2 full blisters + 5 loose
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-25T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data[0].plannerUsage).toBe(25);
      expect(data[0].blisterSize).toBe(10);
      expect(data[0].blistersNeeded).toBe(3); // ceil(25/10)
      expect(data[0].fullBlisters).toBe(2); // floor(25/10)
      expect(data[0].loosePills).toBe(5); // 25 % 10
    });
  });

  // ---------------------------------------------------------------------------
  // Manual Mode Tests
  // ---------------------------------------------------------------------------

  describe("Manual mode", () => {
    beforeEach(async () => {
      await setUserSettings(ctx.client, {
        userId,
        stockCalculationMode: "manual",
      });
    });

    it("should count only tracked doses", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");
      const medId = await createTestMedication(ctx.client, {
        userId,
        name: "Manual Med",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 30,
        blisters: [{ usage: 1, every: 1, start: start.toISOString() }],
      });

      // In automatic mode this would count 10 doses
      // In manual mode, only count tracked doses
      // Track only 3 doses
      const jan2 = Math.floor(new Date("2025-01-02T08:00:00.000Z").getTime() / 1000);
      const jan5 = Math.floor(new Date("2025-01-05T08:00:00.000Z").getTime() / 1000);
      const jan8 = Math.floor(new Date("2025-01-08T08:00:00.000Z").getTime() / 1000);

      await createTestDoseTracking(ctx.client, {
        userId,
        doseId: `${medId}-0-${jan2 * 1000}`,
        takenAt: jan2,
      });
      await createTestDoseTracking(ctx.client, {
        userId,
        doseId: `${medId}-0-${jan5 * 1000}`,
        takenAt: jan5,
      });
      await createTestDoseTracking(ctx.client, {
        userId,
        doseId: `${medId}-0-${jan8 * 1000}`,
        takenAt: jan8,
      });

      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data[0].plannerUsage).toBe(3); // Only 3 tracked doses
    });

    it("should return 0 usage when no doses tracked", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");
      await createTestMedication(ctx.client, {
        userId,
        name: "Untracked Med",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 30,
        blisters: [{ usage: 1, every: 1, start: start.toISOString() }],
      });

      // No dose tracking records
      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data[0].plannerUsage).toBe(0);
      expect(data[0].enough).toBe(true);
    });

    it("should only count doses within date range", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");
      const medId = await createTestMedication(ctx.client, {
        userId,
        name: "Range Test",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 30,
        blisters: [{ usage: 1, every: 1, start: start.toISOString() }],
      });

      // Dose before range (Dec 31)
      const dec31 = Math.floor(new Date("2024-12-31T08:00:00.000Z").getTime() / 1000);
      await createTestDoseTracking(ctx.client, {
        userId,
        doseId: `${medId}-0-${dec31 * 1000}`,
        takenAt: dec31,
      });

      // Dose in range (Jan 5)
      const jan5 = Math.floor(new Date("2025-01-05T08:00:00.000Z").getTime() / 1000);
      await createTestDoseTracking(ctx.client, {
        userId,
        doseId: `${medId}-0-${jan5 * 1000}`,
        takenAt: jan5,
      });

      // Dose after range (Jan 15)
      const jan15 = Math.floor(new Date("2025-01-15T08:00:00.000Z").getTime() / 1000);
      await createTestDoseTracking(ctx.client, {
        userId,
        doseId: `${medId}-0-${jan15 * 1000}`,
        takenAt: jan15,
      });

      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data[0].plannerUsage).toBe(1); // Only Jan 5 is in range
    });

    it("should handle multi-pill doses correctly", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");
      const medId = await createTestMedication(ctx.client, {
        userId,
        name: "Multi-Pill",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 30,
        blisters: [{ usage: 2, every: 1, start: start.toISOString() }], // 2 pills per dose
      });

      const jan2 = Math.floor(new Date("2025-01-02T08:00:00.000Z").getTime() / 1000);
      await createTestDoseTracking(ctx.client, {
        userId,
        doseId: `${medId}-0-${jan2 * 1000}`, // Blister index 0 has usage=2
        takenAt: jan2,
      });

      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data[0].plannerUsage).toBe(2); // 1 dose × 2 pills
    });
  });

  // ---------------------------------------------------------------------------
  // Mode Comparison Tests
  // ---------------------------------------------------------------------------

  describe("Automatic vs Manual mode comparison", () => {
    it("should show different results for same medication", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");
      const medId = await createTestMedication(ctx.client, {
        userId,
        name: "Comparison Med",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 30,
        blisters: [{ usage: 1, every: 1, start: start.toISOString() }],
      });

      // Track only 5 of the 10 scheduled doses
      for (let day = 1; day <= 5; day++) {
        const date = new Date(`2025-01-0${day}T08:00:00.000Z`);
        const ts = Math.floor(date.getTime() / 1000);
        await createTestDoseTracking(ctx.client, {
          userId,
          doseId: `${medId}-0-${ts * 1000}`,
          takenAt: ts,
        });
      }

      // Test automatic mode
      await setUserSettings(ctx.client, {
        userId,
        stockCalculationMode: "automatic",
      });

      const autoResponse = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(autoResponse.statusCode).toBe(200);
      const autoData = autoResponse.json();
      expect(autoData[0].plannerUsage).toBe(10); // Schedule says 10 doses

      // Test manual mode
      await setUserSettings(ctx.client, {
        userId,
        stockCalculationMode: "manual",
      });

      const manualResponse = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(manualResponse.statusCode).toBe(200);
      const manualData = manualResponse.json();
      expect(manualData[0].plannerUsage).toBe(5); // Only 5 actually tracked
    });
  });

  // ---------------------------------------------------------------------------
  // Multiple Medications Tests
  // ---------------------------------------------------------------------------

  describe("Multiple medications", () => {
    it("should calculate usage for all medications", async () => {
      const start = new Date("2025-01-01T00:00:00.000Z");

      await createTestMedication(ctx.client, {
        userId,
        name: "Med A",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 20,
        blisters: [{ usage: 1, every: 1, start: start.toISOString() }],
      });

      await createTestMedication(ctx.client, {
        userId,
        name: "Med B",
        packCount: 1,
        blistersPerPack: 1,
        pillsPerBlister: 20,
        blisters: [{ usage: 2, every: 2, start: start.toISOString() }],
      });

      await setUserSettings(ctx.client, {
        userId,
        stockCalculationMode: "automatic",
      });

      const response = await ctx.app.inject({
        method: "POST",
        url: "/medications/usage",
        payload: {
          startDate: "2025-01-01T00:00:00.000Z",
          endDate: "2025-01-10T23:59:59.999Z",
        },
      });

      expect(response.statusCode).toBe(200);
      const data = response.json();
      expect(data).toHaveLength(2);

      const medA = data.find((d: any) => d.medicationName === "Med A");
      const medB = data.find((d: any) => d.medicationName === "Med B");

      expect(medA.plannerUsage).toBe(10); // 10 days × 1 pill
      expect(medB.plannerUsage).toBe(10); // 5 doses × 2 pills
    });
  });
});
