import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import Fastify from 'fastify';
import { fastifyAwilixPlugin } from '@fastify/awilix';
import { serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';

vi.mock('jose', () => ({
  createRemoteJWKSet: vi.fn(() => 'mock-jwks'),
  jwtVerify: vi.fn().mockResolvedValue({
    payload: {
      sub: 'kc-1',
      email: 'test@example.com',
      preferred_username: 'testuser',
      realm_access: { roles: ['member'] },
      householdIds: ['hh1'],
    },
    protectedHeader: { alg: 'RS256' },
    key: {},
  }),
}));

const {
  mockGetAlerts,
  mockCreateList,
  mockList,
  mockGetById,
  mockUpdateList,
  mockUpdateItem,
  mockAddToCabinet,
  mockGetStoreComparison,
} = vi.hoisted(() => ({
  mockGetAlerts: vi.fn(),
  mockCreateList: vi.fn(),
  mockList: vi.fn(),
  mockGetById: vi.fn(),
  mockUpdateList: vi.fn(),
  mockUpdateItem: vi.fn(),
  mockAddToCabinet: vi.fn(),
  mockGetStoreComparison: vi.fn(),
}));

vi.mock('../../../src/modules/refills/refills.repository.js', () => ({
  RefillsRepository: class {
    create = vi.fn();
    findByHousehold = vi.fn();
    findById = vi.fn();
    update = vi.fn();
    updateItem = vi.fn();
    markItemsAddedToCabinet = vi.fn();
  },
}));

vi.mock('../../../src/modules/refills/refills.service.js', () => ({
  RefillsService: class {
    getAlerts = mockGetAlerts;
    createList = mockCreateList;
    list = mockList;
    getById = mockGetById;
    updateList = mockUpdateList;
    updateItem = mockUpdateItem;
    addToCabinet = mockAddToCabinet;
    getStoreComparison = mockGetStoreComparison;
  },
}));

vi.mock('../../../src/modules/users/users.repository.js', () => ({
  UsersRepository: class {
    findByKeycloakId = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
    upsertFromToken = vi.fn().mockResolvedValue({ householdIds: ['hh1'] });
  },
}));

import authPlugin from '../../../src/plugins/auth.plugin.js';
import householdPlugin from '../../../src/plugins/household.plugin.js';
import usersRoutes from '../../../src/modules/users/users.routes.js';
import refillsRoutes from '../../../src/modules/refills/refills.routes.js';

function makeFakeRefillList(overrides = {}) {
  return {
    _id: 'rl-1',
    householdId: 'hh1',
    name: 'Monthly Refills',
    items: [],
    status: 'active',
    createdBy: 'kc-1',
    createdAt: '2026-01-01T00:00:00.000Z',
    updatedAt: '2026-01-01T00:00:00.000Z',
    ...overrides,
  };
}

describe('refills.routes', () => {
  let app: Awaited<ReturnType<typeof buildTestApp>>;

  async function buildTestApp() {
    const instance = Fastify({ logger: false });
    instance.setValidatorCompiler(validatorCompiler);
    instance.setSerializerCompiler(serializerCompiler);
    await instance.register(fastifyAwilixPlugin, {
      disposeOnClose: true,
      disposeOnResponse: true,
      strictBooleanEnforced: true,
    });
    await instance.register(authPlugin);
    await instance.register(householdPlugin);
    await instance.register(usersRoutes);
    await instance.register(refillsRoutes);
    await instance.ready();
    return instance;
  }

  const authHeaders = { authorization: 'Bearer valid-token' };

  beforeEach(async () => {
    vi.clearAllMocks();
    app = await buildTestApp();
  });

  afterEach(async () => {
    if (app) await app.close();
  });

  describe('GET /api/v1/households/:householdId/refills/alerts', () => {
    it('returns alerts with price options', async () => {
      mockGetAlerts.mockResolvedValue([
        {
          medicineId: 'med-1',
          medicineName: 'Aspirin',
          medicineStrength: 500,
          medicineStrengthUnit: 'mg',
          daysUntilEmpty: 3,
          dailyConsumption: 2,
          currentStock: 6,
          suggestedQuantity: 60,
          lastKnownPrice: {
            price: 10,
            pricePerUnit: 0.1,
            storeName: 'CVS',
            storeId: 'st-1',
            date: new Date('2026-01-01T00:00:00.000Z'),
          },
          cheapestOption: {
            price: 8,
            pricePerUnit: 0.08,
            storeName: 'Walmart',
            storeId: 'st-2',
            date: new Date('2026-01-02T00:00:00.000Z'),
          },
        },
      ]);

      const res = await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/alerts',
        headers: authHeaders,
      });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.data[0].lastKnownPrice.storeName).toBe('CVS');
      expect(body.data[0].lastKnownPrice.date).toBe('2026-01-01T00:00:00.000Z');
      expect(body.data[0].cheapestOption.storeName).toBe('Walmart');
    });

    it('returns alerts', async () => {
      mockGetAlerts.mockResolvedValue([
        {
          medicineId: 'med-1',
          medicineName: 'Aspirin',
          medicineStrength: 500,
          medicineStrengthUnit: 'mg',
          daysUntilEmpty: 3,
          dailyConsumption: 2,
          currentStock: 6,
          suggestedQuantity: 60,
        },
      ]);

      const res = await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/alerts',
        headers: authHeaders,
      });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.data).toHaveLength(1);
      expect(body.data[0].medicineName).toBe('Aspirin');
    });

    it('uses requesting user by default', async () => {
      mockGetAlerts.mockResolvedValue([]);

      await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/alerts',
        headers: authHeaders,
      });

      expect(mockGetAlerts).toHaveBeenCalledWith('hh1', 'kc-1', 7);
    });

    it('uses userId query param when provided', async () => {
      mockGetAlerts.mockResolvedValue([]);

      await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/alerts?userId=other-user&thresholdDays=14',
        headers: authHeaders,
      });

      expect(mockGetAlerts).toHaveBeenCalledWith('hh1', 'other-user', 14);
    });
  });

  describe('POST /api/v1/households/:householdId/refills/lists', () => {
    it('creates list and returns 201', async () => {
      mockCreateList.mockResolvedValue(makeFakeRefillList());

      const res = await app.inject({
        method: 'POST',
        url: '/api/v1/households/hh1/refills/lists',
        headers: authHeaders,
        payload: { name: 'Monthly Refills', fromAlerts: false, thresholdDays: 7 },
      });

      expect(res.statusCode).toBe(201);
      expect(res.json().name).toBe('Monthly Refills');
    });

    it('passes householdId and userId to service', async () => {
      mockCreateList.mockResolvedValue(makeFakeRefillList());

      await app.inject({
        method: 'POST',
        url: '/api/v1/households/hh1/refills/lists',
        headers: authHeaders,
        payload: { name: 'Auto List', fromAlerts: true, thresholdDays: 7 },
      });

      expect(mockCreateList).toHaveBeenCalledWith(
        expect.objectContaining({ name: 'Auto List', fromAlerts: true }),
        'hh1',
        'kc-1',
      );
    });

    it('returns 400 for missing name', async () => {
      const res = await app.inject({
        method: 'POST',
        url: '/api/v1/households/hh1/refills/lists',
        headers: authHeaders,
        payload: {},
      });

      expect(res.statusCode).toBe(400);
    });
  });

  describe('GET /api/v1/households/:householdId/refills/lists', () => {
    it('returns paginated lists', async () => {
      mockList.mockResolvedValue({
        data: [makeFakeRefillList()],
        pagination: { cursor: null, hasMore: false },
      });

      const res = await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/lists',
        headers: authHeaders,
      });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.data).toHaveLength(1);
      expect(body.pagination.hasMore).toBe(false);
    });

    it('includes optional list fields in response', async () => {
      mockList.mockResolvedValue({
        data: [
          makeFakeRefillList({
            preferredStoreId: 'st-1',
            totalEstimatedCost: 25.5,
            items: [
              {
                _id: 'item-1',
                medicineId: 'med-1',
                medicineName: 'Aspirin',
                quantity: 30,
                unit: 'tablet',
                estimatedPrice: 10,
                actualPrice: 9.5,
                checked: true,
                checkedAt: new Date('2026-01-10T00:00:00.000Z'),
                addedToCabinet: false,
                storeId: 'st-1',
                notes: 'generic brand',
              },
            ],
          }),
        ],
        pagination: { cursor: null, hasMore: false },
      });

      const res = await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/lists',
        headers: authHeaders,
      });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.data[0].preferredStoreId).toBe('st-1');
      expect(body.data[0].totalEstimatedCost).toBe(25.5);
      const item = body.data[0].items[0];
      expect(item.estimatedPrice).toBe(10);
      expect(item.actualPrice).toBe(9.5);
      expect(item.checkedAt).toBe('2026-01-10T00:00:00.000Z');
      expect(item.storeId).toBe('st-1');
      expect(item.notes).toBe('generic brand');
    });
  });

  describe('GET /api/v1/households/:householdId/refills/lists/:id', () => {
    it('returns single list', async () => {
      mockGetById.mockResolvedValue(makeFakeRefillList());

      const res = await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/lists/rl-1',
        headers: authHeaders,
      });

      expect(res.statusCode).toBe(200);
      expect(res.json().name).toBe('Monthly Refills');
    });

    it('handles ObjectId-style _id in list and items', async () => {
      mockGetById.mockResolvedValue(
        makeFakeRefillList({
          _id: { toString: () => 'rl-obj' },
          createdAt: new Date('2026-01-01T00:00:00.000Z'),
          updatedAt: new Date('2026-01-01T00:00:00.000Z'),
          items: [
            {
              _id: { toString: () => 'item-obj' },
              medicineId: 'med-1',
              medicineName: 'Aspirin',
              quantity: 30,
              unit: 'tablet',
              checked: false,
              addedToCabinet: false,
            },
          ],
        }),
      );

      const res = await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/lists/rl-1',
        headers: authHeaders,
      });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body._id).toBe('rl-obj');
      expect(body.items[0]._id).toBe('item-obj');
      expect(body.createdAt).toBe('2026-01-01T00:00:00.000Z');
    });

    it('passes id and householdId to service', async () => {
      mockGetById.mockResolvedValue(makeFakeRefillList());

      await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/lists/rl-99',
        headers: authHeaders,
      });

      expect(mockGetById).toHaveBeenCalledWith('rl-99', 'hh1');
    });
  });

  describe('PATCH /api/v1/households/:householdId/refills/lists/:id', () => {
    it('updates list and returns 200', async () => {
      mockUpdateList.mockResolvedValue(makeFakeRefillList({ name: 'Updated' }));

      const res = await app.inject({
        method: 'PATCH',
        url: '/api/v1/households/hh1/refills/lists/rl-1',
        headers: authHeaders,
        payload: { name: 'Updated' },
      });

      expect(res.statusCode).toBe(200);
      expect(res.json().name).toBe('Updated');
    });

    it('passes id, householdId, body to service', async () => {
      mockUpdateList.mockResolvedValue(makeFakeRefillList());

      await app.inject({
        method: 'PATCH',
        url: '/api/v1/households/hh1/refills/lists/rl-1',
        headers: authHeaders,
        payload: { status: 'shopping' },
      });

      expect(mockUpdateList).toHaveBeenCalledWith(
        'rl-1',
        'hh1',
        expect.objectContaining({ status: 'shopping' }),
      );
    });
  });

  describe('PATCH /api/v1/households/:householdId/refills/lists/:id/items/:itemId', () => {
    it('updates item and returns 200', async () => {
      mockUpdateItem.mockResolvedValue(makeFakeRefillList());

      const res = await app.inject({
        method: 'PATCH',
        url: '/api/v1/households/hh1/refills/lists/rl-1/items/item-1',
        headers: authHeaders,
        payload: { checked: true },
      });

      expect(res.statusCode).toBe(200);
    });

    it('passes listId, householdId, itemId, body to service', async () => {
      mockUpdateItem.mockResolvedValue(makeFakeRefillList());

      await app.inject({
        method: 'PATCH',
        url: '/api/v1/households/hh1/refills/lists/rl-1/items/item-99',
        headers: authHeaders,
        payload: { actualPrice: 9.99 },
      });

      expect(mockUpdateItem).toHaveBeenCalledWith(
        'rl-1',
        'hh1',
        'item-99',
        expect.objectContaining({ actualPrice: 9.99 }),
      );
    });
  });

  describe('POST /api/v1/households/:householdId/refills/lists/:id/add-to-cabinet', () => {
    it('adds items to cabinet and returns summary', async () => {
      mockAddToCabinet.mockResolvedValue({ addedCount: 2, priceRecordsCreated: 0 });

      const res = await app.inject({
        method: 'POST',
        url: '/api/v1/households/hh1/refills/lists/rl-1/add-to-cabinet',
        headers: authHeaders,
      });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.addedCount).toBe(2);
      expect(body.priceRecordsCreated).toBe(0);
    });

    it('passes listId, householdId, userId to service', async () => {
      mockAddToCabinet.mockResolvedValue({ addedCount: 0, priceRecordsCreated: 0 });

      await app.inject({
        method: 'POST',
        url: '/api/v1/households/hh1/refills/lists/rl-1/add-to-cabinet',
        headers: authHeaders,
      });

      expect(mockAddToCabinet).toHaveBeenCalledWith('rl-1', 'hh1', 'kc-1');
    });
  });

  describe('GET /api/v1/households/:householdId/refills/lists/:id/store-comparison', () => {
    it('returns store comparison data', async () => {
      mockGetStoreComparison.mockResolvedValue([
        {
          medicineId: 'med-1',
          storeOptions: [
            {
              storeId: 'st-1',
              storeName: 'CVS',
              latestPrice: 8,
              latestPricePerUnit: 0.08,
              currency: 'USD',
              date: new Date('2026-01-01T00:00:00.000Z'),
              isInsurancePrice: false,
            },
          ],
        },
      ]);

      const res = await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/lists/rl-1/store-comparison',
        headers: authHeaders,
      });

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.data).toHaveLength(1);
      expect(body.data[0].storeOptions[0].date).toBe('2026-01-01T00:00:00.000Z');
    });

    it('passes listId and householdId to service', async () => {
      mockGetStoreComparison.mockResolvedValue([]);

      await app.inject({
        method: 'GET',
        url: '/api/v1/households/hh1/refills/lists/rl-99/store-comparison',
        headers: authHeaders,
      });

      expect(mockGetStoreComparison).toHaveBeenCalledWith('rl-99', 'hh1');
    });
  });
});
