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

const repoMock = {
  createOrganization: vi.fn(),
  listOrganizations: vi.fn(),
  getOrganization: vi.fn(),
  updateOrganization: vi.fn(),
  deleteOrganization: vi.fn(),
  createWorkspace: vi.fn(),
  listWorkspaces: vi.fn(),
  getWorkspace: vi.fn(),
  updateWorkspace: vi.fn(),
  deleteWorkspace: vi.fn(),
  createMembership: vi.fn(),
  listMemberships: vi.fn(),
  getMembership: vi.fn(),
  updateMembership: vi.fn(),
  deleteMembership: vi.fn(),
  getUserMembership: vi.fn(),
};

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

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

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

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

  it('POST /orgs creates an organization and owner membership', async () => {
    repoMock.createOrganization.mockResolvedValue({
      id: 'org_1',
      name: 'Acme',
      ownerUserId: 'admin_1',
    });
    repoMock.createMembership.mockResolvedValue({ id: 'mbr_1' });

    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });
    const res = await app.inject({
      method: 'POST',
      url: '/api/orgs',
      payload: { name: 'Acme', slug: 'acme' },
    });

    expect(res.statusCode).toBe(200);
    expect(repoMock.createOrganization).toHaveBeenCalled();
    expect(repoMock.createMembership).toHaveBeenCalledWith(
      expect.objectContaining({
        scope: 'org',
        role: 'owner',
        userId: 'admin_1',
      })
    );
  });

  it('POST /orgs/:id/memberships validates workspace scope', async () => {
    repoMock.getOrganization.mockResolvedValue({ id: 'org_1' });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/orgs/org_1/memberships',
      payload: { userId: 'user_2', role: 'member', scope: 'workspace' },
    });

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

  it('DELETE /orgs/:id only allows owner', async () => {
    repoMock.getOrganization.mockResolvedValue({
      id: 'org_1',
      productId: 'lysnrai',
      ownerUserId: 'someone_else',
    });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'DELETE',
      url: '/api/orgs/org_1',
    });

    expect(res.statusCode).toBe(403);
    expect(repoMock.deleteOrganization).not.toHaveBeenCalled();
  });

  it('DELETE /orgs/:id succeeds for owner', async () => {
    repoMock.getOrganization.mockResolvedValue({
      id: 'org_1',
      productId: 'lysnrai',
      ownerUserId: 'admin_1',
    });
    repoMock.deleteOrganization.mockResolvedValue(undefined);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'DELETE',
      url: '/api/orgs/org_1',
    });

    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body)).toEqual({ deleted: true });
  });

  it('DELETE /orgs/:id/memberships/:membershipId prevents removing owner', async () => {
    repoMock.getMembership.mockResolvedValue({ id: 'mbr_1', role: 'owner', orgId: 'org_1' });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'DELETE',
      url: '/api/orgs/org_1/memberships/mbr_1',
    });

    expect(res.statusCode).toBe(403);
    expect(repoMock.deleteMembership).not.toHaveBeenCalled();
  });

  it('DELETE /orgs/:id/memberships/:membershipId succeeds when actor outranks target', async () => {
    repoMock.getMembership.mockResolvedValue({ id: 'mbr_2', role: 'member', orgId: 'org_1' });
    repoMock.getUserMembership.mockResolvedValue({ id: 'mbr_1', role: 'admin', orgId: 'org_1' });
    repoMock.deleteMembership.mockResolvedValue(undefined);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'DELETE',
      url: '/api/orgs/org_1/memberships/mbr_2',
    });

    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.body)).toEqual({ deleted: true });
  });

  it('GET /orgs/:id/permissions returns role permissions', async () => {
    repoMock.getUserMembership.mockResolvedValue({
      id: 'mbr_1',
      role: 'admin',
      orgId: 'org_1',
      userId: 'user_1',
    });
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'GET',
      url: '/api/orgs/org_1/permissions?userId=user_1',
    });

    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body);
    expect(body.role).toBe('admin');
    expect(body.permissions).toContain('org:read');
    expect(body.permissions).toContain('member:invite');
    expect(body.permissions).not.toContain('org:delete');
  });

  it('GET /orgs/:id/permissions returns empty for non-member', async () => {
    repoMock.getUserMembership.mockResolvedValue(null);
    const app = await buildApp({ sub: 'admin_1', productId: 'lysnrai', role: 'admin' });

    const res = await app.inject({
      method: 'GET',
      url: '/api/orgs/org_1/permissions?userId=unknown',
    });

    expect(res.statusCode).toBe(200);
    const body = JSON.parse(res.body);
    expect(body.role).toBeNull();
    expect(body.permissions).toEqual([]);
  });
});
