import { describe, expect, it, vi, beforeAll, afterAll, beforeEach } from 'vitest';
import { createServiceApp, type FastifyApp } from '@bytelyst/fastify-core';

vi.mock('../../lib/config.js', () => ({
  config: {
    SERVICE_NAME: 'cowork-service',
    PLATFORM_SERVICE_URL: 'http://mock-platform:4003',
  },
}));
vi.mock('../../lib/product-config.js', () => ({
  PRODUCT_ID: 'clawcowork',
}));
vi.mock('../../lib/request-context.js', () => ({
  getUserId: vi.fn(() => 'demo-user'),
}));

import { usageRoutes } from './routes.js';

let app: FastifyApp;

const mockFetch = vi.fn();
vi.stubGlobal('fetch', mockFetch);

beforeAll(async () => {
  app = await createServiceApp({
    name: 'usage-routes-test',
    version: '0.0.1',
    logger: false,
  });
  await app.register(usageRoutes);
});

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

beforeEach(() => {
  mockFetch.mockReset();
});

describe('usage proxy routes', () => {
  describe('GET /api/usage/summary', () => {
    it('proxies summary request with userId and days', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({ totalTokens: 5000, totalCost: 0.12, days: 7 }),
      });

      const res = await app.inject({ method: 'GET', url: '/api/usage/summary?days=7' });

      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.payload);
      expect(body.totalTokens).toBe(5000);
      const fetchUrl = mockFetch.mock.calls[0][0] as string;
      expect(fetchUrl).toContain('userId=demo-user');
      expect(fetchUrl).toContain('days=7');
    });

    it('returns 502 when platform-service is unreachable', async () => {
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

      const res = await app.inject({ method: 'GET', url: '/api/usage/summary' });

      expect(res.statusCode).toBe(502);
      const body = JSON.parse(res.payload);
      expect(body.error).toBe('Platform-service unavailable');
    });

    it('forwards platform non-ok status', async () => {
      mockFetch.mockResolvedValue({ ok: false, status: 500 });

      const res = await app.inject({ method: 'GET', url: '/api/usage/summary' });

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

  describe('POST /api/usage/check-limits', () => {
    it('proxies check-limits with userId and plan', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({ exceeded: [], warnings: [], withinLimits: true }),
      });

      const res = await app.inject({
        method: 'POST',
        url: '/api/usage/check-limits',
        payload: { plan: 'pro' },
      });

      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.payload);
      expect(body.withinLimits).toBe(true);
      const fetchBody = JSON.parse(mockFetch.mock.calls[0][1].body);
      expect(fetchBody.userId).toBe('demo-user');
      expect(fetchBody.plan).toBe('pro');
    });

    it('returns 502 when platform-service is unreachable', async () => {
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

      const res = await app.inject({
        method: 'POST',
        url: '/api/usage/check-limits',
        payload: { plan: 'free' },
      });

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

  describe('GET /api/usage/budget-policy', () => {
    it('loads active daily and monthly product policies', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => [
          { id: 'day_1', period: 'daily', budgetUsd: 5, modelAllowlist: ['gpt-4o-mini'] },
          { id: 'month_1', period: 'monthly', budgetUsd: 50, modelAllowlist: ['gpt-4o-mini'] },
        ],
      });

      const res = await app.inject({
        method: 'GET',
        url: '/api/usage/budget-policy',
        headers: { authorization: 'Bearer test-token' },
      });

      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.payload);
      expect(body.dailyBudgetUsd).toBe(5);
      expect(body.monthlyBudgetUsd).toBe(50);
      expect((mockFetch.mock.calls[0][1].headers as Record<string, string>).authorization).toBe(
        'Bearer test-token'
      );
    });

    it('forwards budget-policy authorization failures', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
        status: 403,
        json: async () => ({ error: 'Admin access required' }),
      });

      const res = await app.inject({
        method: 'GET',
        url: '/api/usage/budget-policy',
      });

      expect(res.statusCode).toBe(403);
      expect(JSON.parse(res.payload).error).toBe('Admin access required');
    });
  });

  describe('PUT /api/usage/budget-policy', () => {
    it('creates daily and monthly policies when missing', async () => {
      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: async () => [],
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ id: 'day_1', period: 'daily', budgetUsd: 10 }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ id: 'month_1', period: 'monthly', budgetUsd: 200 }),
        });

      const res = await app.inject({
        method: 'PUT',
        url: '/api/usage/budget-policy',
        headers: { authorization: 'Bearer admin-token' },
        payload: {
          dailyBudgetUsd: 10,
          monthlyBudgetUsd: 200,
          modelAllowlist: ['gpt-4o-mini', 'claude-sonnet-4-20250514'],
        },
      });

      expect(res.statusCode).toBe(200);
      const body = JSON.parse(res.payload);
      expect(body.dailyBudgetUsd).toBe(10);
      expect(body.monthlyBudgetUsd).toBe(200);
      expect(mockFetch.mock.calls[1][1].method).toBe('POST');
      expect(mockFetch.mock.calls[2][1].method).toBe('POST');
    });

    it('updates existing policies when present', async () => {
      mockFetch
        .mockResolvedValueOnce({
          ok: true,
          json: async () => [
            { id: 'day_1', period: 'daily', budgetUsd: 5 },
            { id: 'month_1', period: 'monthly', budgetUsd: 50 },
          ],
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ id: 'day_1', period: 'daily', budgetUsd: 15 }),
        })
        .mockResolvedValueOnce({
          ok: true,
          json: async () => ({ id: 'month_1', period: 'monthly', budgetUsd: 150 }),
        });

      const res = await app.inject({
        method: 'PUT',
        url: '/api/usage/budget-policy',
        payload: {
          dailyBudgetUsd: 15,
          monthlyBudgetUsd: 150,
          modelAllowlist: ['gpt-4o-mini'],
        },
      });

      expect(res.statusCode).toBe(200);
      expect(mockFetch.mock.calls[1][1].method).toBe('PATCH');
      expect(mockFetch.mock.calls[2][1].method).toBe('PATCH');
    });
  });
});
