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

// --- Mock Supabase client before any imports ---
// supabase.ts throws at module load if env vars are missing,
// so we mock the entire module here.
//
// The chain used in licence.ts for reading:
//   supabase.from('licences').select('*').eq('key_hash', hash).single()
//
// For updating:
//   supabase.from('licences').update({...}).eq('id', id)

// Hoisted mocks so they're available in the vi.mock factory
const mockSingle = vi.hoisted(() => vi.fn());
const mockUpdate = vi.hoisted(() => vi.fn());
const mockFrom = vi.hoisted(() => vi.fn());
const mockRpc = vi.hoisted(() => vi.fn());

vi.mock('../lib/supabase.js', () => {
  return {
    supabase: {
      from: mockFrom,
      rpc: mockRpc,
    },
  };
});

// Import after mocks are registered
import { validateLicence } from '../lib/licence.js';
import type { LicenceRecord } from '../types/index.js';

function makeLicence(overrides: Partial<LicenceRecord> = {}): LicenceRecord {
  return {
    id: 'uuid-1234',
    key_hash: 'somehash',
    email: 'test@example.com',
    plan: 'pro',
    quota_month: 100,
    usage_month: 5,
    reset_at: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
    active: true,
    allowed_ip: null,
    ...overrides,
  };
}

/**
 * Setup the from() chain for a SELECT query:
 * from(table).select('*').eq(col, val).single() → resolves to { data, error }
 */
function setupSelectChain(resolvedValue: { data: LicenceRecord | null; error: unknown }) {
  mockSingle.mockResolvedValue(resolvedValue);

  const eqChain = { single: mockSingle };
  const selectChain = { eq: vi.fn().mockReturnValue(eqChain) };

  // For update chain: from(table).update({...}).eq(col, val)
  const updateEqFn = vi.fn().mockResolvedValue({ error: null });
  mockUpdate.mockReturnValue({ eq: updateEqFn });

  mockFrom.mockImplementation((table: string) => {
    if (table === 'licences') {
      return {
        select: vi.fn().mockReturnValue(selectChain),
        update: mockUpdate,
      };
    }
    // usage_logs table (used in incrementUsage, not validateLicence)
    return {
      insert: vi.fn().mockResolvedValue({ error: null }),
      select: vi.fn().mockReturnValue({
        eq: vi.fn().mockReturnValue({
          gte: vi.fn().mockResolvedValue({ count: 0 }),
        }),
      }),
      update: vi.fn().mockReturnValue({
        eq: vi.fn().mockResolvedValue({ error: null }),
      }),
    };
  });

  return { updateEqFn };
}

describe('validateLicence', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockRpc.mockResolvedValue({ error: null });
  });

  it('returns { valid: false, reason: "invalid" } when key does not exist', async () => {
    setupSelectChain({ data: null, error: { message: 'not found' } });

    const result = await validateLicence('LAU-XXXX-XXXX-XXXX', '1.2.3.4');
    expect(result).toEqual({ valid: false, reason: 'invalid' });
  });

  it('returns { valid: false, reason: "inactive" } when licence.active is false', async () => {
    const licence = makeLicence({ active: false });
    setupSelectChain({ data: licence, error: null });

    const result = await validateLicence('LAU-ABCD-1234-WXYZ', '1.2.3.4');
    expect(result).toEqual({ valid: false, reason: 'inactive' });
  });

  it('returns { valid: false, reason: "quota_exceeded" } when usage >= quota', async () => {
    const licence = makeLicence({ usage_month: 100, quota_month: 100 });
    setupSelectChain({ data: licence, error: null });

    const result = await validateLicence('LAU-ABCD-1234-WXYZ', '1.2.3.4');
    expect(result).toEqual({ valid: false, reason: 'quota_exceeded' });
  });

  it('returns { valid: true, licence } when licence is valid and quota available', async () => {
    const licence = makeLicence({ usage_month: 5, quota_month: 100 });
    setupSelectChain({ data: licence, error: null });

    const result = await validateLicence('LAU-ABCD-1234-WXYZ', '1.2.3.4');
    expect(result).toMatchObject({ valid: true });
    if (result.valid) {
      expect(result.licence.id).toBe('uuid-1234');
    }
  });

  it('returns { valid: false, reason: "ip_mismatch" } when IP does not match allowed_ip', async () => {
    const licence = makeLicence({ allowed_ip: '10.0.0.1' });
    setupSelectChain({ data: licence, error: null });

    const result = await validateLicence('LAU-ABCD-1234-WXYZ', '192.168.1.100');
    expect(result).toEqual({ valid: false, reason: 'ip_mismatch' });
  });

  it('allows request when IP matches allowed_ip exactly', async () => {
    const licence = makeLicence({ allowed_ip: '10.0.0.1' });
    setupSelectChain({ data: licence, error: null });

    const result = await validateLicence('LAU-ABCD-1234-WXYZ', '10.0.0.1');
    expect(result).toMatchObject({ valid: true });
  });

  it('resets usage_month to 0 when reset_at is in the past', async () => {
    // reset_at is in the past → monthly reset triggers, usage_month → 0
    const licence = makeLicence({
      usage_month: 95,
      quota_month: 100,
      reset_at: new Date(Date.now() - 1000).toISOString(),
    });
    const { updateEqFn } = setupSelectChain({ data: licence, error: null });

    const result = await validateLicence('LAU-ABCD-1234-WXYZ', '1.2.3.4');

    // After reset, usage_month = 0 < quota_month 100 → valid
    expect(result).toMatchObject({ valid: true });

    // Verify the update was called with usage_month: 0
    expect(mockUpdate).toHaveBeenCalledWith(
      expect.objectContaining({ usage_month: 0 })
    );
    // Verify the eq clause targeted the right licence id
    expect(updateEqFn).toHaveBeenCalledWith('id', 'uuid-1234');
  });
});
