import { ForbiddenException } from '@nestjs/common';
import { describe, expect, it, vi } from 'vitest';
import { noModuleAccess } from '../src/common/access/modules';
import type { AuthenticatedUser } from '../src/common/auth/authenticated-user';
import { StateController } from '../src/modules/state/state.controller';
import type { StateService } from '../src/modules/state/state.service';

function userWith(level: 'READ' | 'WRITE'): AuthenticatedUser {
  return {
    id: '22222222-2222-4222-8222-222222222222',
    email: 'customer@example.test',
    displayName: 'Customer',
    role: 'USER',
    isReadOnly: false,
  subscriptionTier: null,
    accessExpiresAt: null,
    sessionId: 'session',
    modules: { ...noModuleAccess(), trading: level, portfolio: level },
  };
}

describe('StateController module write enforcement', () => {
  it('rejects a direct write to the trading blob for READ users', async () => {
    const state = { set: vi.fn() };
    const controller = new StateController(state as unknown as StateService);

    await expect(controller.set(
      userWith('READ'),
      'ck_trading_terminal_v10_30_live',
      { value: '{}' },
    )).rejects.toBeInstanceOf(ForbiddenException);
    expect(state.set).not.toHaveBeenCalled();
  });

  it('allows WRITE users to persist the portfolio blob', async () => {
    const state = { set: vi.fn().mockResolvedValue(undefined) };
    const controller = new StateController(state as unknown as StateService);

    await controller.set(
      userWith('WRITE'),
      'ck_portfolio_terminal_v6',
      { value: '{}' },
    );

    expect(state.set).toHaveBeenCalledOnce();
  });

  it('rejects reading a gated blob for a user with no access to that module', async () => {
    const state = { get: vi.fn() };
    const controller = new StateController(state as unknown as StateService);
    const noAccess: AuthenticatedUser = { ...userWith('READ'), modules: noModuleAccess() };

    await expect(controller.get(noAccess, 'ck_portfolio_terminal_v6'))
      .rejects.toBeInstanceOf(ForbiddenException);
    expect(state.get).not.toHaveBeenCalled();
  });

  it('allows READ users to load a gated blob', async () => {
    const state = { get: vi.fn().mockResolvedValue('{}') };
    const controller = new StateController(state as unknown as StateService);

    const result = await controller.get(userWith('READ'), 'ck_trading_terminal_v10_30_live');

    expect(result).toEqual({ value: '{}' });
    expect(state.get).toHaveBeenCalledOnce();
  });

  it('leaves an unlisted key ungated, so every module without a dedicated blob still works', async () => {
    const state = { get: vi.fn().mockResolvedValue(null), set: vi.fn().mockResolvedValue(undefined) };
    const controller = new StateController(state as unknown as StateService);
    const noAccess: AuthenticatedUser = { ...userWith('READ'), modules: noModuleAccess() };

    await controller.get(noAccess, 'ck_some_unlisted_key');
    await controller.set(noAccess, 'ck_some_unlisted_key', { value: '{}' });

    expect(state.get).toHaveBeenCalledOnce();
    expect(state.set).toHaveBeenCalledOnce();
  });

  it.each([
    ['ck_custom_lists', 'market'],
    ['ck_capital_management_money_flows_v1', 'capital'],
    ['ckBusinessCockpitV6', 'management'],
  ] as const)('gates the %s blob on the %s module', async (key, moduleName) => {
    const state = { get: vi.fn().mockResolvedValue(null), set: vi.fn().mockResolvedValue(undefined) };
    const controller = new StateController(state as unknown as StateService);
    const noAccess: AuthenticatedUser = { ...userWith('READ'), modules: noModuleAccess() };
    const readOnlyAccess: AuthenticatedUser = {
      ...userWith('READ'),
      modules: { ...noModuleAccess(), [moduleName]: 'READ' },
    };

    await expect(controller.get(noAccess, key)).rejects.toBeInstanceOf(ForbiddenException);
    await expect(controller.set(readOnlyAccess, key, { value: '{}' }))
      .rejects.toBeInstanceOf(ForbiddenException);

    await controller.get(readOnlyAccess, key);
    expect(state.get).toHaveBeenCalledOnce();
  });
});
