import { describe, it, expect, vi, afterEach } from 'vitest';
import { createOrgClient } from './client.js';
import type { OrganizationDoc, WorkspaceDoc, MembershipDoc, LicenseDoc } from './types.js';

const baseConfig = {
  baseUrl: 'http://localhost:4003/api',
  productId: 'testapp',
  getAccessToken: () => 'admin-token',
};

function mockOrg(overrides?: Partial<OrganizationDoc>): OrganizationDoc {
  return {
    id: 'org_1',
    productId: 'testapp',
    name: 'Test Org',
    slug: 'test-org',
    status: 'active',
    ownerUserId: 'user-1',
    createdAt: '2026-01-01T00:00:00Z',
    updatedAt: '2026-01-01T00:00:00Z',
    ...overrides,
  };
}

function mockWorkspace(overrides?: Partial<WorkspaceDoc>): WorkspaceDoc {
  return {
    id: 'ws_1',
    orgId: 'org_1',
    productId: 'testapp',
    name: 'Engineering',
    slug: 'engineering',
    status: 'active',
    createdAt: '2026-01-01T00:00:00Z',
    updatedAt: '2026-01-01T00:00:00Z',
    ...overrides,
  };
}

function mockMembership(overrides?: Partial<MembershipDoc>): MembershipDoc {
  return {
    id: 'mbr_org1_user1_org',
    orgId: 'org_1',
    productId: 'testapp',
    scope: 'org',
    userId: 'user-1',
    role: 'admin',
    status: 'active',
    createdAt: '2026-01-01T00:00:00Z',
    updatedAt: '2026-01-01T00:00:00Z',
    ...overrides,
  };
}

function mockLicense(overrides?: Partial<LicenseDoc>): LicenseDoc {
  return {
    id: 'lic_1',
    productId: 'testapp',
    key: 'LYSNR-XXXX-YYYY-ZZZZ',
    userId: 'user-1',
    plan: 'pro',
    status: 'active',
    activatedAt: '2026-01-01T00:00:00Z',
    expiresAt: null,
    deviceIds: ['device-1'],
    maxDevices: 3,
    createdAt: '2026-01-01T00:00:00Z',
    updatedAt: '2026-01-01T00:00:00Z',
    ...overrides,
  };
}

describe('createOrgClient', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should list orgs', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve([mockOrg()]),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.listOrgs();
    expect(result).toHaveLength(1);
    expect(result[0].name).toBe('Test Org');
  });

  it('should create an org', async () => {
    const org = mockOrg();
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(org),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.createOrg({ name: 'Test Org', slug: 'test-org' });
    expect(result.id).toBe('org_1');
  });

  it('should get an org by id', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockOrg()),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.getOrg('org_1');
    expect(result.slug).toBe('test-org');
  });

  it('should list workspaces for an org', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve([mockWorkspace()]),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.listWorkspaces('org_1');
    expect(result).toHaveLength(1);
    expect(result[0].name).toBe('Engineering');
  });

  it('should create a workspace', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockWorkspace()),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.createWorkspace('org_1', {
      name: 'Engineering',
      slug: 'engineering',
    });
    expect(result.orgId).toBe('org_1');
  });

  it('should list memberships', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve([mockMembership()]),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.listMemberships('org_1');
    expect(result).toHaveLength(1);
    expect(result[0].role).toBe('admin');
  });

  it('should add a member', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockMembership()),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.addMember('org_1', { userId: 'user-1', role: 'admin' });
    expect(result.userId).toBe('user-1');
  });

  it('should generate a license', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockLicense()),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.generateLicense({ userId: 'user-1', plan: 'pro' });
    expect(result.key).toContain('LYSNR');
  });

  it('should activate a license', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(mockLicense()),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.activateLicense({ key: 'LYSNR-XXXX', deviceId: 'device-1' });
    expect(result.status).toBe('active');
  });

  it('should update an org', async () => {
    const org = mockOrg({ name: 'Updated Org' });
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(org),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.updateOrg('org_1', { name: 'Updated Org' });
    expect(result.name).toBe('Updated Org');

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/orgs/org_1',
      expect.objectContaining({ method: 'PATCH' })
    );
  });

  it('should update a workspace', async () => {
    const ws = mockWorkspace({ name: 'Renamed' });
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve(ws),
      })
    );
    const client = createOrgClient(baseConfig);
    const result = await client.updateWorkspace('org_1', 'ws_1', { name: 'Renamed' });
    expect(result.name).toBe('Renamed');

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/orgs/org_1/workspaces/ws_1',
      expect.objectContaining({ method: 'PATCH' })
    );
  });

  it('should deactivate a license', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
      })
    );
    const client = createOrgClient(baseConfig);
    await expect(
      client.deactivateLicense({ key: 'LYSNR-XXXX', deviceId: 'device-1' })
    ).resolves.toBeUndefined();

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    expect(fetchMock).toHaveBeenCalledWith(
      'http://localhost:4003/api/licenses/deactivate',
      expect.objectContaining({ method: 'POST' })
    );
  });

  it('should throw 403 for non-admin', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: false,
        status: 403,
      })
    );
    const client = createOrgClient(baseConfig);
    await expect(client.listOrgs()).rejects.toThrow('listOrgs failed: 403');
  });

  it('should send correct headers', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn().mockResolvedValue({
        ok: true,
        json: () => Promise.resolve([]),
      })
    );
    const client = createOrgClient(baseConfig);
    await client.listOrgs();

    const fetchMock = globalThis.fetch as ReturnType<typeof vi.fn>;
    const callHeaders = fetchMock.mock.calls[0][1].headers as Record<string, string>;
    expect(callHeaders['x-product-id']).toBe('testapp');
    expect(callHeaders['Authorization']).toBe('Bearer admin-token');
    expect(callHeaders['x-request-id']).toBeDefined();
  });
});
