/**
 * Route-level tests for license activation behavior.
 */

import Fastify from 'fastify';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const requestContextMock = {
  getRequestProductId: vi.fn(),
  getRequestProductConfig: vi.fn(),
};

const repoMock = {
  generateKey: vi.fn(),
  create: vi.fn(),
  getByKey: vi.fn(),
  update: vi.fn(),
  getByUserId: vi.fn(),
};

const authRepoMock = {
  getById: vi.fn(),
};

const jwtMock = {
  createAccessToken: vi.fn(),
  createRefreshToken: vi.fn(),
};

vi.mock('../../lib/request-context.js', () => requestContextMock);
vi.mock('./repository.js', () => repoMock);
vi.mock('../auth/repository.js', () => authRepoMock);
vi.mock('../auth/jwt.js', () => jwtMock);

describe('licenseRoutes activate', () => {
  beforeEach(() => {
    vi.clearAllMocks();

    requestContextMock.getRequestProductId.mockReturnValue('lysnrai');
    requestContextMock.getRequestProductConfig.mockReturnValue({
      deviceLimits: { free: 1, pro: 3, enterprise: 10 },
    });

    authRepoMock.getById.mockResolvedValue({
      id: 'usr_1',
      email: 'u@test.com',
      role: 'user',
      status: 'active',
    });

    jwtMock.createAccessToken.mockResolvedValue('access-token');
    jwtMock.createRefreshToken.mockResolvedValue('refresh-token');
  });

  it('issues tokens and enforces product-config device limit on activation', async () => {
    repoMock.getByKey.mockResolvedValue({
      id: 'lic_1',
      productId: 'lysnrai',
      key: 'LYSNR-ABCD-EFGH-IJKL',
      userId: 'usr_1',
      plan: 'pro',
      status: 'active',
      activatedAt: null,
      expiresAt: null,
      deviceIds: [],
      maxDevices: 1,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    });

    repoMock.update.mockResolvedValue({
      id: 'lic_1',
      productId: 'lysnrai',
      key: 'LYSNR-ABCD-EFGH-IJKL',
      userId: 'usr_1',
      plan: 'pro',
      status: 'active',
      activatedAt: new Date().toISOString(),
      expiresAt: null,
      deviceIds: ['mac-1'],
      maxDevices: 3,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
    });

    const { licenseRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(licenseRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/licenses/activate',
      payload: { key: 'LYSNR-ABCD-EFGH-IJKL', deviceId: 'mac-1' },
    });

    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.accessToken).toBe('access-token');
    expect(body.refreshToken).toBe('refresh-token');

    expect(repoMock.update).toHaveBeenCalledTimes(1);
    expect(repoMock.update.mock.calls[0][2]).toMatchObject({
      maxDevices: 3,
    });

    await app.close();
  });

  it('locks out IP after repeated failed activation attempts', async () => {
    repoMock.getByKey.mockResolvedValue(null);

    const { licenseRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(licenseRoutes, { prefix: '/api' });

    for (let i = 0; i < 5; i += 1) {
      const res = await app.inject({
        method: 'POST',
        url: '/api/licenses/activate',
        payload: { key: 'missing', deviceId: 'mac-1' },
      });
      expect(res.statusCode).toBe(404);
    }

    const locked = await app.inject({
      method: 'POST',
      url: '/api/licenses/activate',
      payload: { key: 'missing', deviceId: 'mac-1' },
    });

    expect(locked.statusCode).toBe(400);
    const body = locked.json() as { message?: string; error?: { message?: string } };
    const msg = body.error?.message ?? body.message ?? '';
    expect(msg).toContain('Too many failed activation attempts');

    await app.close();
  });
});
