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

const repoMock = {
  listCases: vi.fn(),
  createCase: vi.fn(),
  getCase: vi.fn(),
  updateCase: vi.fn(),
  createNote: vi.fn(),
  listNotes: vi.fn(),
  createEscalation: vi.fn(),
  listEscalations: vi.fn(),
  listAllCases: vi.fn(),
};

vi.mock('./repository.js', () => repoMock);

async function buildApp(payload?: { sub: string; productId: string; role?: string }) {
  const { supportCaseRoutes } = await import('./routes.js');
  const app = Fastify({ logger: false });
  if (payload) {
    app.addHook('onRequest', async req => {
      req.jwtPayload = payload;
    });
  }
  await app.register(supportCaseRoutes, { prefix: '/api' });
  return app;
}

describe('supportCaseRoutes', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('POST /support/cases creates an agent-linked case', async () => {
    repoMock.createCase.mockResolvedValue({ id: 'sup_1' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/support/cases',
      payload: {
        title: 'Incident escalation',
        source: 'agent',
        runId: 'run_1',
        reviewId: 'rev_1',
      },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.createCase).toHaveBeenCalledWith(
      expect.objectContaining({
        source: 'agent',
        status: 'triaged',
        runId: 'run_1',
      })
    );
  });

  it('POST /support/cases/:id/notes adds a note to an existing case', async () => {
    repoMock.getCase.mockResolvedValue({ id: 'sup_1', productId: 'lysnrai' });
    repoMock.createNote.mockResolvedValue({ id: 'sup_1:note:1' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/support/cases/sup_1/notes',
      payload: {
        authorId: 'admin_1',
        body: 'Reached out to the customer.',
      },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.createNote).toHaveBeenCalledWith(
      expect.objectContaining({
        caseId: 'sup_1',
        authorId: 'admin_1',
      })
    );
  });

  it('POST /support/cases/:id/escalations updates the case and records the event', async () => {
    repoMock.getCase.mockResolvedValue({ id: 'sup_1', productId: 'lysnrai' });
    repoMock.updateCase.mockResolvedValue({ id: 'sup_1', status: 'escalated' });
    repoMock.createEscalation.mockResolvedValue({ id: 'sup_1:esc:1' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/support/cases/sup_1/escalations',
      payload: {
        escalatedTo: 'tier2',
        reason: 'Customer impact confirmed',
      },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.updateCase).toHaveBeenCalledWith('sup_1', 'lysnrai', { status: 'escalated' });
    expect(repoMock.createEscalation).toHaveBeenCalledWith(
      expect.objectContaining({
        caseId: 'sup_1',
        escalatedTo: 'tier2',
      })
    );
  });

  // ── Case Timeline ───────────────────────────────────────

  it('GET /support/cases/:id/timeline returns chronological events', async () => {
    repoMock.getCase.mockResolvedValue({
      id: 'sup_1',
      productId: 'lysnrai',
      title: 'Test',
      priority: 'high',
      source: 'manual',
      requesterUserId: 'user_1',
      createdAt: '2026-03-15T00:00:00Z',
    });
    repoMock.listNotes.mockResolvedValue([
      {
        authorId: 'admin_1',
        authorType: 'user',
        visibility: 'internal',
        body: 'Investigating',
        createdAt: '2026-03-15T01:00:00Z',
      },
    ]);
    repoMock.listEscalations.mockResolvedValue([
      {
        triggeredBy: 'admin_1',
        escalatedTo: 'tier2',
        reason: 'Critical',
        createdAt: '2026-03-15T02:00:00Z',
      },
    ]);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'GET', url: '/api/support/cases/sup_1/timeline' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.events).toHaveLength(3);
    expect(body.events[0].type).toBe('case_created');
    expect(body.events[1].type).toBe('internal_note');
    expect(body.events[2].type).toBe('escalation');
  });

  // ── SLA Engine ─────────────────────────────────────────

  it('GET /support/cases/:id/sla returns SLA status with breach detection', async () => {
    repoMock.getCase.mockResolvedValue({
      id: 'sup_1',
      productId: 'lysnrai',
      priority: 'critical',
      status: 'open',
      createdAt: '2026-03-15T00:00:00Z',
      updatedAt: '2026-03-15T00:00:00Z',
    });
    repoMock.listNotes.mockResolvedValue([
      { authorType: 'user', createdAt: '2026-03-15T00:30:00Z' },
    ]);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'GET', url: '/api/support/cases/sup_1/sla' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.priority).toBe('critical');
    expect(body.response.hasResponse).toBe(true);
    expect(body.target.responseHours).toBe(1);
  });

  // ── Auto-Triage ────────────────────────────────────────

  it('POST /support/cases/:id/auto-triage upgrades priority for outage keywords', async () => {
    repoMock.getCase.mockResolvedValue({
      id: 'sup_1',
      productId: 'lysnrai',
      title: 'Production outage affecting all users',
      description: 'Login service is down',
      priority: 'medium',
      tags: [],
    });
    repoMock.updateCase.mockImplementation(
      async (_id: string, _pid: string, updates: Record<string, unknown>) => ({
        ...updates,
        id: 'sup_1',
      })
    );
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'POST', url: '/api/support/cases/sup_1/auto-triage' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.triaged).toBe(true);
    expect(body.newPriority).toBe('critical');
    expect(body.tagsAdded).toContain('auto-triaged:critical');
    expect(body.tagsAdded).toContain('category:auth');
  });

  it('POST /support/cases/:id/auto-triage detects billing category', async () => {
    repoMock.getCase.mockResolvedValue({
      id: 'sup_2',
      productId: 'lysnrai',
      title: 'Question about billing',
      description: 'How do I update payment method?',
      priority: 'medium',
      tags: [],
    });
    repoMock.updateCase.mockImplementation(
      async (_id: string, _pid: string, updates: Record<string, unknown>) => ({
        ...updates,
        id: 'sup_2',
      })
    );
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'POST', url: '/api/support/cases/sup_2/auto-triage' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.newPriority).toBe('low');
    expect(body.tagsAdded).toContain('category:billing');
  });

  it('POST /support/cases/:id/auto-triage does not regress status of in-progress cases', async () => {
    repoMock.getCase.mockResolvedValue({
      id: 'sup_4',
      productId: 'lysnrai',
      title: 'Error on login page',
      description: 'Users see 500',
      priority: 'medium',
      status: 'in_progress',
      tags: [],
    });
    repoMock.updateCase.mockImplementation(
      async (_id: string, _pid: string, updates: Record<string, unknown>) => ({
        ...updates,
        id: 'sup_4',
      })
    );
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'POST', url: '/api/support/cases/sup_4/auto-triage' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.newPriority).toBe('high');
    // Status should remain in_progress, not regress to triaged
    expect(repoMock.updateCase).toHaveBeenCalledWith(
      'sup_4',
      'lysnrai',
      expect.objectContaining({ status: 'in_progress' })
    );
  });

  it('POST /support/cases/:id/auto-triage handles undefined tags gracefully', async () => {
    repoMock.getCase.mockResolvedValue({
      id: 'sup_3',
      productId: 'lysnrai',
      title: 'Slow dashboard loading',
      description: 'Pages take 10s to load',
      priority: 'low',
      // tags intentionally omitted — undefined
    });
    repoMock.updateCase.mockImplementation(
      async (_id: string, _pid: string, updates: Record<string, unknown>) => ({
        ...updates,
        id: 'sup_3',
      })
    );
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'POST', url: '/api/support/cases/sup_3/auto-triage' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.newPriority).toBe('medium');
    expect(body.tagsAdded).toContain('auto-triaged:medium');
  });

  // ── Case Metrics ───────────────────────────────────────

  it('GET /support/metrics returns case metrics with SLA compliance', async () => {
    repoMock.listAllCases.mockResolvedValue([
      {
        id: 'sup_1',
        status: 'open',
        priority: 'high',
        source: 'manual',
        createdAt: '2026-03-15T00:00:00Z',
        updatedAt: '2026-03-15T00:00:00Z',
      },
      {
        id: 'sup_2',
        status: 'resolved',
        priority: 'medium',
        source: 'agent',
        createdAt: '2026-03-14T00:00:00Z',
        updatedAt: '2026-03-14T02:00:00Z',
      },
      {
        id: 'sup_3',
        status: 'closed',
        priority: 'low',
        source: 'customer',
        createdAt: '2026-03-13T00:00:00Z',
        updatedAt: '2026-03-13T12:00:00Z',
      },
    ]);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'GET', url: '/api/support/metrics' });
    expect(res.statusCode).toBe(200);
    const body = res.json();
    expect(body.total).toBe(3);
    expect(body.open).toBe(1);
    expect(body.resolved).toBe(2);
    expect(body.byStatus.open).toBe(1);
    expect(body.slaComplianceRate).toBe(100);
  });
});
