import { describe, it, expect, vi, beforeEach } from 'vitest';
import Fastify from 'fastify';
import { fastifyAwilixPlugin } from '@fastify/awilix';
import { asValue } from 'awilix';

// Mock jose for the auth plugin dependency
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'] },
    },
    protectedHeader: { alg: 'RS256' },
    key: {},
  }),
}));

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

import authPlugin from '../../src/plugins/auth.plugin.js';
import householdPlugin from '../../src/plugins/household.plugin.js';

describe('household.plugin', () => {
  async function buildApp() {
    const app = Fastify({ logger: false });
    await app.register(fastifyAwilixPlugin, {
      disposeOnClose: true,
      disposeOnResponse: true,
      strictBooleanEnforced: true,
    });
    app.diContainer.register({
      usersRepository: asValue({
        findByKeycloakId: mockFindByKeycloakId,
        upsertFromToken: vi.fn().mockResolvedValue({ householdIds: ['hh1'] }),
      }),
    });
    await app.register(authPlugin);
    await app.register(householdPlugin);
    return app;
  }

  beforeEach(() => {
    vi.clearAllMocks();
    mockFindByKeycloakId.mockResolvedValue({ householdIds: ['hh1'] });
  });

  it('skips household check for public routes', async () => {
    const app = await buildApp();
    app.get('/public', { config: { public: true } as never }, async () => ({ ok: true }));
    await app.ready();

    const res = await app.inject({ method: 'GET', url: '/public' });
    expect(res.statusCode).toBe(200);
  });

  it('skips household check for routes with skipHousehold', async () => {
    const app = await buildApp();
    app.get('/skip', { config: { skipHousehold: true } as never }, async () => ({ ok: true }));
    await app.ready();

    const res = await app.inject({
      method: 'GET',
      url: '/skip',
      headers: { authorization: 'Bearer valid' },
    });
    expect(res.statusCode).toBe(200);
  });

  it('throws 403 when user does not belong to household', async () => {
    const app = await buildApp();
    app.get('/households/:householdId/data', async () => ({ ok: true }));
    await app.ready();

    const res = await app.inject({
      method: 'GET',
      url: '/households/hh-unknown/data',
      headers: { authorization: 'Bearer valid' },
    });
    expect(res.statusCode).toBe(403);
    expect(res.json().message).toContain('do not belong');
  });

  it('sets request.householdId when user belongs to household', async () => {
    const app = await buildApp();
    let capturedId: string | undefined;
    app.get('/households/:householdId/data', async (request) => {
      capturedId = request.householdId;
      return { ok: true };
    });
    await app.ready();

    const res = await app.inject({
      method: 'GET',
      url: '/households/hh1/data',
      headers: { authorization: 'Bearer valid' },
    });
    expect(res.statusCode).toBe(200);
    expect(capturedId).toBe('hh1');
  });

  it('skips household check when route has no householdId param', async () => {
    const app = await buildApp();
    app.get('/no-household', async () => ({ ok: true }));
    await app.ready();

    const res = await app.inject({
      method: 'GET',
      url: '/no-household',
      headers: { authorization: 'Bearer valid' },
    });
    expect(res.statusCode).toBe(200);
  });
});
