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 {
  mockFindByHousehold,
  mockFindById,
  mockFindExpiringSoon,
  mockCreate,
  mockUpdate,
  mockDelete,
  mockGetWasteStats,
  mockGetTopWastedProducts,
  mockFindByIds,
  mockBulkUpdateStatus,
  mockFindActiveByHousehold,
  mockUpdateFreshness,
} = vi.hoisted(() => ({
  mockFindByHousehold: vi.fn(),
  mockFindById: vi.fn(),
  mockFindExpiringSoon: vi.fn(),
  mockCreate: vi.fn(),
  mockUpdate: vi.fn(),
  mockDelete: vi.fn(),
  mockGetWasteStats: vi.fn(),
  mockGetTopWastedProducts: vi.fn(),
  mockFindByIds: vi.fn(),
  mockBulkUpdateStatus: vi.fn(),
  mockFindActiveByHousehold: vi.fn(),
  mockUpdateFreshness: vi.fn(),
}));

const { mockProductFindById } = vi.hoisted(() => ({
  mockProductFindById: vi.fn(),
}));

const { mockFindApplicableRule } = vi.hoisted(() => ({
  mockFindApplicableRule: vi.fn(),
}));

vi.mock('../../../src/modules/pantry/pantry.repository.js', () => ({
  PantryRepository: class {
    findByHousehold = mockFindByHousehold;
    findById = mockFindById;
    findExpiringSoon = mockFindExpiringSoon;
    findActiveByHousehold = mockFindActiveByHousehold;
    create = mockCreate;
    update = mockUpdate;
    updateFreshness = mockUpdateFreshness;
    delete = mockDelete;
    getWasteStats = mockGetWasteStats;
    getTopWastedProducts = mockGetTopWastedProducts;
    findByIds = mockFindByIds;
    bulkUpdateStatus = mockBulkUpdateStatus;
  },
}));

vi.mock('../../../src/modules/products/products.repository.js', () => ({
  ProductsRepository: class {
    findById = mockProductFindById;
    findByIds = vi.fn();
  },
}));

vi.mock('../../../src/modules/freshness-rules/freshness-rules.repository.js', () => ({
  FreshnessRulesRepository: class {
    findApplicableRule = mockFindApplicableRule;
    findByHousehold = vi.fn();
    findById = vi.fn();
    create = vi.fn();
    update = vi.fn();
    delete = vi.fn();
  },
}));

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 pantryRoutes from '../../../src/modules/pantry/pantry.routes.js';

const freshness = {
  estimatedExpiryDate: new Date('2024-02-01').toISOString(),
  daysRemaining: 14,
  urgency: 'fresh',
  source: 'rule',
};

function makeItem(overrides: Record<string, unknown> = {}) {
  return {
    _id: 'item-1',
    householdId: 'hh1',
    productId: 'p1',
    productName: 'Milk',
    storageLocation: 'fridge',
    quantity: 1,
    unit: 'piece',
    purchaseDate: new Date('2024-01-01').toISOString(),
    status: 'sealed',
    freshnessEstimate: freshness,
    createdBy: 'kc-1',
    createdAt: new Date().toISOString(),
    updatedAt: new Date().toISOString(),
    ...overrides,
  };
}

describe('pantry.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(pantryRoutes);
    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 /pantry', () => {
    it('returns paginated list', async () => {
      mockFindByHousehold.mockResolvedValue({
        data: [makeItem()],
        pagination: { cursor: null, hasMore: false },
      });

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

      expect(res.statusCode).toBe(200);
      expect(res.json().data).toHaveLength(1);
    });

    it('returns empty list', async () => {
      mockFindByHousehold.mockResolvedValue({
        data: [],
        pagination: { cursor: null, hasMore: false },
      });

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

      expect(res.statusCode).toBe(200);
      expect(res.json().data).toHaveLength(0);
    });
  });

  describe('GET /pantry/expiring-soon', () => {
    it('returns expiring items', async () => {
      mockFindExpiringSoon.mockResolvedValue({
        data: [makeItem()],
        pagination: { cursor: null, hasMore: false },
      });

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

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

  describe('GET /pantry/stats', () => {
    it('returns waste stats', async () => {
      mockGetWasteStats.mockResolvedValue([{ totalConsumed: 5, totalDiscarded: 2 }]);
      mockGetTopWastedProducts.mockResolvedValue([]);

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

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.totalItemsConsumed).toBe(5);
      expect(body.wastePercentage).toBeCloseTo(28.57, 1);
    });
  });

  describe('GET /pantry/:id', () => {
    it('returns item', async () => {
      mockFindById.mockResolvedValue(makeItem());

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

      expect(res.statusCode).toBe(200);
      expect(res.json().productName).toBe('Milk');
    });

    it('returns item with all optional fields', async () => {
      mockFindById.mockResolvedValue(
        makeItem({
          expirationDate: new Date('2024-02-01').toISOString(),
          openedDate: new Date('2024-01-05').toISOString(),
          preparedDate: new Date('2024-01-06').toISOString(),
          notes: 'Organic',
          purchasePrice: 4.99,
          storeId: 's1',
        }),
      );

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

      expect(res.statusCode).toBe(200);
      const body = res.json();
      expect(body.expirationDate).toBeDefined();
      expect(body.openedDate).toBeDefined();
      expect(body.preparedDate).toBeDefined();
      expect(body.notes).toBe('Organic');
      expect(body.purchasePrice).toBe(4.99);
      expect(body.storeId).toBe('s1');
    });

    it('returns 404 when not found', async () => {
      mockFindById.mockResolvedValue(null);

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

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

  describe('POST /pantry', () => {
    it('creates a pantry item', async () => {
      mockProductFindById.mockResolvedValue({
        _id: 'p1',
        name: 'Milk',
        category: 'dairy',
      });
      mockFindApplicableRule.mockResolvedValue({ shelfLifeDays: 14, openedLifeDays: 7 });
      mockCreate.mockResolvedValue(makeItem());

      const res = await app.inject({
        method: 'POST',
        url: '/api/v1/households/hh1/pantry',
        headers: { ...authHeaders, 'content-type': 'application/json' },
        body: JSON.stringify({
          productId: 'p1',
          storageLocation: 'fridge',
          quantity: 1,
          unit: 'piece',
        }),
      });

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

    it('rejects missing productId', async () => {
      const res = await app.inject({
        method: 'POST',
        url: '/api/v1/households/hh1/pantry',
        headers: { ...authHeaders, 'content-type': 'application/json' },
        body: JSON.stringify({
          storageLocation: 'fridge',
          quantity: 1,
          unit: 'piece',
        }),
      });

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

  describe('PATCH /pantry/:id', () => {
    it('updates a pantry item', async () => {
      mockFindById.mockResolvedValue(makeItem());
      mockUpdate.mockResolvedValue(makeItem({ quantity: 3 }));

      const res = await app.inject({
        method: 'PATCH',
        url: '/api/v1/households/hh1/pantry/item-1',
        headers: { ...authHeaders, 'content-type': 'application/json' },
        body: JSON.stringify({ quantity: 3 }),
      });

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

  describe('POST /pantry/:id/transition', () => {
    it('transitions item status', async () => {
      const item = makeItem({ status: 'sealed' });
      mockFindById.mockResolvedValue(item);
      mockUpdate.mockResolvedValue({ ...item, status: 'consumed' });

      const res = await app.inject({
        method: 'POST',
        url: '/api/v1/households/hh1/pantry/item-1/transition',
        headers: { ...authHeaders, 'content-type': 'application/json' },
        body: JSON.stringify({ status: 'consumed' }),
      });

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

  describe('POST /pantry/batch-transition', () => {
    it('batch transitions items', async () => {
      mockFindByIds.mockResolvedValue([makeItem()]);
      mockBulkUpdateStatus.mockResolvedValue(1);

      const res = await app.inject({
        method: 'POST',
        url: '/api/v1/households/hh1/pantry/batch-transition',
        headers: { ...authHeaders, 'content-type': 'application/json' },
        body: JSON.stringify({
          itemIds: ['item-1'],
          status: 'consumed',
        }),
      });

      expect(res.statusCode).toBe(200);
      expect(res.json().transitioned).toBe(1);
    });
  });

  describe('DELETE /pantry/:id', () => {
    it('deletes a pantry item', async () => {
      mockFindById.mockResolvedValue(makeItem());
      mockDelete.mockResolvedValue(makeItem());

      const res = await app.inject({
        method: 'DELETE',
        url: '/api/v1/households/hh1/pantry/item-1',
        headers: authHeaders,
      });

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