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

const repoMock = {
  listConnectors: vi.fn(),
  createConnector: vi.fn(),
  getConnector: vi.fn(),
  updateConnector: vi.fn(),
  deleteConnector: vi.fn(),
  upsertUserSync: vi.fn(),
  listUserSync: vi.fn(),
  upsertGroupSync: vi.fn(),
  listGroupSync: vi.fn(),
  createEvent: vi.fn(),
  listEvents: vi.fn(),
  getConnectorStats: vi.fn(),
};

const orgRepoMock = {
  getOrganization: vi.fn(),
};

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

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

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

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

  it('POST /scim/connectors creates a connector for an existing org', async () => {
    orgRepoMock.getOrganization.mockResolvedValue({ id: 'org_1' });
    repoMock.createConnector.mockResolvedValue({ id: 'scim_1' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/scim/connectors',
      payload: {
        orgId: 'org_1',
        name: 'Okta',
        baseUrl: 'https://okta.example.com/scim/v2',
      },
    });

    expect(res.statusCode).toBe(200);
    expect(orgRepoMock.getOrganization).toHaveBeenCalledWith('org_1', 'lysnrai');
    expect(repoMock.createConnector).toHaveBeenCalledWith(
      expect.objectContaining({
        orgId: 'org_1',
        name: 'Okta',
      })
    );
  });

  it('POST /scim/connectors/:orgId/:id/users upserts user sync state', async () => {
    repoMock.getConnector.mockResolvedValue({ id: 'scim_1' });
    repoMock.upsertUserSync.mockResolvedValue({ id: 'scim_1:user:ext_1' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/scim/connectors/org_1/scim_1/users',
      payload: {
        externalUserId: 'ext_1',
        email: 'member@acme.com',
        status: 'provisioned',
      },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.upsertUserSync).toHaveBeenCalledWith(
      expect.objectContaining({
        connectorId: 'scim_1',
        externalUserId: 'ext_1',
      })
    );
  });

  it('POST /scim/connectors/:orgId/:id/events records provisioning events', async () => {
    repoMock.getConnector.mockResolvedValue({ id: 'scim_1' });
    repoMock.createEvent.mockResolvedValue({ id: 'scimevt_1' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/scim/connectors/org_1/scim_1/events',
      payload: {
        entityType: 'user',
        entityId: 'ext_1',
        action: 'provision',
        status: 'success',
      },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.createEvent).toHaveBeenCalledWith(
      expect.objectContaining({
        connectorId: 'scim_1',
        entityType: 'user',
        action: 'provision',
      })
    );
  });

  it('GET /scim/connectors/:orgId/:id/stats returns connector stats', async () => {
    repoMock.getConnector.mockResolvedValue({ id: 'scim_1', orgId: 'org_1' });
    repoMock.getConnectorStats.mockResolvedValue({
      connectorId: 'scim_1',
      userCount: 10,
      groupCount: 3,
      eventCount: 25,
      provisionedUsers: 8,
      failedUsers: 2,
      provisionedGroups: 3,
      failedGroups: 0,
      lastEventAt: '2026-03-20T00:00:00.000Z',
    });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({ method: 'GET', url: '/api/scim/connectors/org_1/scim_1/stats' });

    expect(res.statusCode).toBe(200);
    expect(res.json().userCount).toBe(10);
    expect(res.json().failedUsers).toBe(2);
  });

  it('POST /scim/connectors/:orgId/:id/pause pauses an active connector', async () => {
    repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'active' });
    repoMock.updateConnector.mockResolvedValue({ id: 'scim_1', status: 'paused' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/scim/connectors/org_1/scim_1/pause',
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.updateConnector).toHaveBeenCalledWith('scim_1', 'org_1', { status: 'paused' });
  });

  it('POST /scim/connectors/:orgId/:id/pause rejects non-active connector', async () => {
    repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'paused' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/scim/connectors/org_1/scim_1/pause',
    });

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

  it('POST /scim/connectors/:orgId/:id/resume resumes a paused connector', async () => {
    repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'paused' });
    repoMock.updateConnector.mockResolvedValue({ id: 'scim_1', status: 'active' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/scim/connectors/org_1/scim_1/resume',
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.updateConnector).toHaveBeenCalledWith('scim_1', 'org_1', { status: 'active' });
  });

  it('POST /scim/connectors/:orgId/:id/resume rejects non-paused connector', async () => {
    repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'active' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/scim/connectors/org_1/scim_1/resume',
    });

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

  it('DELETE /scim/connectors/:orgId/:id deletes a paused connector', async () => {
    repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'paused' });
    repoMock.deleteConnector.mockResolvedValue(undefined);

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({ method: 'DELETE', url: '/api/scim/connectors/org_1/scim_1' });

    expect(res.statusCode).toBe(200);
    expect(res.json()).toEqual({ deleted: true });
  });

  it('DELETE /scim/connectors/:orgId/:id rejects active connector', async () => {
    repoMock.getConnector.mockResolvedValue({ id: 'scim_1', status: 'active' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({ method: 'DELETE', url: '/api/scim/connectors/org_1/scim_1' });

    expect(res.statusCode).toBe(400);
    expect(repoMock.deleteConnector).not.toHaveBeenCalled();
  });
});
