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

const repoMock = {
  listAgents: vi.fn(),
  createAgent: vi.fn(),
  getAgent: vi.fn(),
  updateAgent: vi.fn(),
  deleteAgent: vi.fn(),
  createAgentVersion: vi.fn(),
  listAgentVersions: vi.fn(),
  getAgentVersion: vi.fn(),
  getAgentByKey: vi.fn(),
  updateAgentVersion: vi.fn(),
  getPublishedVersion: vi.fn(),
};

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

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

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

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

  it('POST /agents creates an agent with an initial version', async () => {
    repoMock.createAgent.mockResolvedValue({ id: 'agt_1', key: 'incident-responder' });
    repoMock.createAgentVersion.mockResolvedValue({ id: 'agt_1:v1' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/agents',
      payload: {
        key: 'incident-responder',
        name: 'Incident Responder',
        initialVersion: {
          prompt: 'Investigate incidents.',
          toolBindings: ['support.runIncidentPipeline'],
        },
      },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.createAgent).toHaveBeenCalled();
    expect(repoMock.createAgentVersion).toHaveBeenCalledWith(
      expect.objectContaining({
        version: 1,
        prompt: 'Investigate incidents.',
      })
    );
  });

  it('POST /agents/:id/versions creates a new version and advances currentVersion', async () => {
    repoMock.getAgent.mockResolvedValue({
      id: 'agt_1',
      productId: 'lysnrai',
      currentVersion: 1,
      status: 'draft',
    });
    repoMock.createAgentVersion.mockResolvedValue({ id: 'agt_1:v2' });
    repoMock.updateAgent.mockResolvedValue({ id: 'agt_1', currentVersion: 2 });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/agents/agt_1/versions',
      payload: {
        prompt: 'Investigate incidents with tooling.',
        status: 'published',
      },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.createAgentVersion).toHaveBeenCalledWith(
      expect.objectContaining({
        version: 2,
        status: 'published',
      })
    );
    expect(repoMock.updateAgent).toHaveBeenCalledWith(
      'agt_1',
      'lysnrai',
      expect.objectContaining({
        currentVersion: 2,
      })
    );
  });

  it('GET /agents/by-key/:key looks up agent by key', async () => {
    repoMock.getAgentByKey.mockResolvedValue({ id: 'agt_1', key: 'incident-responder' });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'GET', url: '/api/agents/by-key/incident-responder' });
    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body).key).toBe('incident-responder');
  });

  it('GET /agents/by-key/:key returns 404 for unknown key', async () => {
    repoMock.getAgentByKey.mockResolvedValue(null);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'GET', url: '/api/agents/by-key/nonexistent' });
    expect(res.statusCode).toBe(404);
  });

  it('POST /agents/:id/versions/:versionId/publish publishes and deprecates previous', async () => {
    repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai', status: 'active' });
    repoMock.getAgentVersion.mockResolvedValue({ id: 'agt_1:v2', status: 'draft', version: 2 });
    repoMock.getPublishedVersion.mockResolvedValue({ id: 'agt_1:v1', status: 'published' });
    repoMock.updateAgentVersion.mockResolvedValue({ id: 'agt_1:v2', status: 'published' });
    repoMock.updateAgent.mockResolvedValue({ id: 'agt_1' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/agents/agt_1/versions/agt_1:v2/publish',
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.updateAgentVersion).toHaveBeenCalledWith('agt_1:v1', 'agt_1', {
      status: 'deprecated',
    });
    expect(repoMock.updateAgentVersion).toHaveBeenCalledWith('agt_1:v2', 'agt_1', {
      status: 'published',
    });
  });

  it('POST /agents/:id/versions/:versionId/publish rejects already published', async () => {
    repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai' });
    repoMock.getAgentVersion.mockResolvedValue({ id: 'agt_1:v1', status: 'published' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/agents/agt_1/versions/agt_1:v1/publish',
    });

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

  it('POST /agents/:id/versions/:versionId/deprecate marks version deprecated', async () => {
    repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai' });
    repoMock.getAgentVersion.mockResolvedValue({ id: 'agt_1:v1', status: 'published' });
    repoMock.updateAgentVersion.mockResolvedValue({ id: 'agt_1:v1', status: 'deprecated' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/agents/agt_1/versions/agt_1:v1/deprecate',
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.updateAgentVersion).toHaveBeenCalledWith('agt_1:v1', 'agt_1', {
      status: 'deprecated',
    });
  });

  it('POST /agents/:id/versions/:versionId/deprecate rejects already deprecated', async () => {
    repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai' });
    repoMock.getAgentVersion.mockResolvedValue({ id: 'agt_1:v1', status: 'deprecated' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/agents/agt_1/versions/agt_1:v1/deprecate',
    });

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

  it('GET /agents/:id/published returns the published version', async () => {
    repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai' });
    repoMock.getPublishedVersion.mockResolvedValue({
      id: 'agt_1:v2',
      status: 'published',
      version: 2,
    });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({ method: 'GET', url: '/api/agents/agt_1/published' });

    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body).id).toBe('agt_1:v2');
  });

  it('GET /agents/:id/published returns 404 when none published', async () => {
    repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai' });
    repoMock.getPublishedVersion.mockResolvedValue(null);

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({ method: 'GET', url: '/api/agents/agt_1/published' });

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

  it('DELETE /agents/:id only allows deleting draft agents', async () => {
    repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai', status: 'active' });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'DELETE', url: '/api/agents/agt_1' });
    expect(res.statusCode).toBe(400);
    expect(repoMock.deleteAgent).not.toHaveBeenCalled();
  });

  it('DELETE /agents/:id succeeds for draft agent', async () => {
    repoMock.getAgent.mockResolvedValue({ id: 'agt_1', productId: 'lysnrai', status: 'draft' });
    repoMock.deleteAgent.mockResolvedValue(undefined);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({ method: 'DELETE', url: '/api/agents/agt_1' });
    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body)).toEqual({ deleted: true });
  });
});
