/**
 * Permission matrix tests.
 *
 * Tests the withPermission() guard logic directly using mock AuthContext values.
 * These tests do NOT require a database and run always.
 *
 * Verifies:
 *  - super_admin bypasses all permission checks
 *  - viewer can read but not write
 *  - sales_agent can manage own clients/interests but not admin features
 *  - sales_manager has elevated but non-admin access
 *  - director mirrors sales (full sales access, no admin)
 *  - deepMerge correctly applies port-level overrides
 */
import { describe, it, expect, vi } from 'vitest';

import { withPermission, deepMerge, type AuthContext } from '@/lib/api/helpers';
import { CLIENT_ARCHIVE_ACTION } from '@/lib/auth/permissions';
import {
  makeViewerPermissions,
  makeSalesAgentPermissions,
  makeSalesManagerPermissions,
  makeDirectorPermissions,
} from '../helpers/factories';
import type { RolePermissions } from '@/lib/db/schema/users';
import { NextRequest, NextResponse } from 'next/server';

// ─── Helpers ─────────────────────────────────────────────────────────────────

function makeCtx(overrides: Partial<AuthContext>): AuthContext {
  return {
    userId: 'user-1',
    portId: 'port-1',
    portSlug: 'test-port',
    isSuperAdmin: false,
    permissions: makeViewerPermissions(),
    user: { email: 'test@example.com', name: 'Test User', signingEmail: null },
    ipAddress: '127.0.0.1',
    userAgent: 'vitest/1.0',
    ...overrides,
  };
}

/** Minimal NextRequest for testing permission guards. */
function makeRequest(): NextRequest {
  return new NextRequest('http://localhost/api/test', { method: 'GET' });
}

/** Returns a handler that resolves to 200 OK. */
function okHandler() {
  return vi.fn().mockResolvedValue(NextResponse.json({ ok: true }, { status: 200 }));
}

/**
 * Invokes the withPermission guard and returns the response status.
 */
async function checkPermission(
  ctx: AuthContext,
  resource: keyof RolePermissions,
  action: string,
): Promise<number> {
  const handler = okHandler();
  const guarded = withPermission(resource, action, handler);
  const response = await guarded(makeRequest(), ctx, {});
  return response.status;
}

// ─── client archive policy ─────────────────────────────────────────────────────

/**
 * Archiving a client is REVERSIBLE (sets archivedAt; restorable), so it is an
 * edit-level action — NOT the destructive `clients:delete` (which only
 * super_admin holds) and NOT `admin:permanently_delete_clients` (hard delete).
 * Regression guard: prod showed Sales Managers/Directors getting 403 on archive
 * because every archive route gated on `delete`. All archive routes now gate on
 * CLIENT_ARCHIVE_ACTION; if anyone flips it back to a permission the sales roles
 * lack, these fail.
 */
describe('Permission Matrix - client archive policy', () => {
  it('archiving is an edit-level action, not delete', () => {
    expect(CLIENT_ARCHIVE_ACTION).toBe('edit');
  });

  it('a Sales Manager can archive a client', async () => {
    const ctx = makeCtx({ permissions: makeSalesManagerPermissions() });
    expect(await checkPermission(ctx, 'clients', CLIENT_ARCHIVE_ACTION)).toBe(200);
  });

  it('a Director can archive a client', async () => {
    const ctx = makeCtx({ permissions: makeDirectorPermissions() });
    expect(await checkPermission(ctx, 'clients', CLIENT_ARCHIVE_ACTION)).toBe(200);
  });

  it('a Sales Agent can archive a client', async () => {
    const ctx = makeCtx({ permissions: makeSalesAgentPermissions() });
    expect(await checkPermission(ctx, 'clients', CLIENT_ARCHIVE_ACTION)).toBe(200);
  });

  it('a Viewer cannot archive a client', async () => {
    const ctx = makeCtx({ permissions: makeViewerPermissions() });
    expect(await checkPermission(ctx, 'clients', CLIENT_ARCHIVE_ACTION)).toBe(403);
  });
});

// ─── super_admin ──────────────────────────────────────────────────────────────

describe('Permission Matrix - super_admin', () => {
  const ctx = makeCtx({ isSuperAdmin: true, permissions: null });

  it('can access clients.create', async () => {
    expect(await checkPermission(ctx, 'clients', 'create')).toBe(200);
  });

  it('can access admin.manage_users', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_users')).toBe(200);
  });

  it('can access admin.system_backup', async () => {
    expect(await checkPermission(ctx, 'admin', 'system_backup')).toBe(200);
  });

  it('can access invoices.delete', async () => {
    expect(await checkPermission(ctx, 'invoices', 'delete')).toBe(200);
  });
});

// ─── viewer ───────────────────────────────────────────────────────────────────

describe('Permission Matrix - viewer', () => {
  const ctx = makeCtx({ permissions: makeViewerPermissions() });

  it('can view clients', async () => {
    expect(await checkPermission(ctx, 'clients', 'view')).toBe(200);
  });

  it('cannot create clients', async () => {
    expect(await checkPermission(ctx, 'clients', 'create')).toBe(403);
  });

  it('cannot run a GDPR export', async () => {
    expect(await checkPermission(ctx, 'clients', 'gdpr_export')).toBe(403);
  });

  it('cannot update clients', async () => {
    expect(await checkPermission(ctx, 'clients', 'edit')).toBe(403);
  });

  it('cannot delete clients', async () => {
    expect(await checkPermission(ctx, 'clients', 'delete')).toBe(403);
  });

  it('cannot change interest stage', async () => {
    expect(await checkPermission(ctx, 'interests', 'change_stage')).toBe(403);
  });

  it('cannot manage admin settings', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_settings')).toBe(403);
  });

  it('cannot manage webhooks', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_webhooks')).toBe(403);
  });
});

// ─── sales_agent ─────────────────────────────────────────────────────────────

describe('Permission Matrix - sales_agent', () => {
  const ctx = makeCtx({ permissions: makeSalesAgentPermissions() });

  it('can view clients', async () => {
    expect(await checkPermission(ctx, 'clients', 'view')).toBe(200);
  });

  it('can create clients', async () => {
    expect(await checkPermission(ctx, 'clients', 'create')).toBe(200);
  });

  it('can edit clients', async () => {
    expect(await checkPermission(ctx, 'clients', 'edit')).toBe(200);
  });

  it('cannot delete clients', async () => {
    expect(await checkPermission(ctx, 'clients', 'delete')).toBe(403);
  });

  it('cannot merge clients', async () => {
    expect(await checkPermission(ctx, 'clients', 'merge')).toBe(403);
  });

  it('can create interests', async () => {
    expect(await checkPermission(ctx, 'interests', 'create')).toBe(200);
  });

  it('can change interest stage', async () => {
    expect(await checkPermission(ctx, 'interests', 'change_stage')).toBe(200);
  });

  it('cannot manage admin users', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_users')).toBe(403);
  });

  it('cannot manage webhooks', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_webhooks')).toBe(403);
  });

  it('cannot configure email accounts', async () => {
    expect(await checkPermission(ctx, 'email', 'configure_account')).toBe(403);
  });
});

// ─── sales_manager ────────────────────────────────────────────────────────────

describe('Permission Matrix - sales_manager', () => {
  const ctx = makeCtx({ permissions: makeSalesManagerPermissions() });

  it('can do everything with clients', async () => {
    for (const action of ['view', 'create', 'edit', 'delete', 'merge', 'export']) {
      expect(await checkPermission(ctx, 'clients', action)).toBe(200);
    }
  });

  it('can run a GDPR export (clients.gdpr_export)', async () => {
    expect(await checkPermission(ctx, 'clients', 'gdpr_export')).toBe(200);
  });

  it('can view audit log', async () => {
    expect(await checkPermission(ctx, 'admin', 'view_audit_log')).toBe(200);
  });

  it('cannot manage webhooks', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_webhooks')).toBe(403);
  });

  it('cannot manage system users', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_users')).toBe(403);
  });
});

// ─── director (senior-title twin of Sales: full sales, no admin) ──────────────

describe('Permission Matrix - director', () => {
  const ctx = makeCtx({ permissions: makeDirectorPermissions() });

  it('has full sales access (create clients)', async () => {
    expect(await checkPermission(ctx, 'clients', 'create')).toBe(200);
  });

  it('can manage tags', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_tags')).toBe(200);
  });

  it('cannot manage users', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_users')).toBe(403);
  });

  it('cannot manage settings', async () => {
    expect(await checkPermission(ctx, 'admin', 'manage_settings')).toBe(403);
  });

  it('cannot perform system_backup', async () => {
    expect(await checkPermission(ctx, 'admin', 'system_backup')).toBe(403);
  });
});

// ─── deepMerge ────────────────────────────────────────────────────────────────

describe('deepMerge - permission override merging', () => {
  it('overrides a single leaf value', () => {
    const base = { clients: { view: true, create: false } };
    const override = { clients: { create: true } };
    const result = deepMerge(base, override) as typeof base;
    expect(result.clients.create).toBe(true);
    expect(result.clients.view).toBe(true);
  });

  it('does not mutate the base object', () => {
    const base = { a: { b: false } };
    const override = { a: { b: true } };
    deepMerge(base, override);
    expect(base.a.b).toBe(false);
  });

  it('merges nested objects without removing unrelated keys', () => {
    const base = { admin: { manage_users: false, view_audit_log: true } };
    const override = { admin: { manage_users: true } };
    const result = deepMerge(base, override) as typeof base;
    expect(result.admin.manage_users).toBe(true);
    expect(result.admin.view_audit_log).toBe(true);
  });

  it('override with full-permission block gives full access', () => {
    const base = makeViewerPermissions() as Record<string, unknown>;
    const override = {
      clients: { create: true, edit: true, delete: true, merge: true, export: true },
    };
    const result = deepMerge(base, override) as RolePermissions;
    expect(result.clients.create).toBe(true);
    expect(result.clients.view).toBe(true); // preserved from base
  });

  it('handles non-object values (arrays stay as-is)', () => {
    const base = { events: ['a', 'b'] };
    const override = { events: ['c'] };
    const result = deepMerge(base, override) as typeof base;
    expect(result.events).toEqual(['c']);
  });
});

// ─── new resources (yachts, companies, memberships, tenancies) ──────────────

describe('new resources (yachts, companies, memberships, tenancies)', () => {
  it('super_admin bypasses all new resource permissions', async () => {
    const ctx = makeCtx({ isSuperAdmin: true, permissions: null });
    const handler = vi.fn(okHandler());
    const wrapped = withPermission('yachts', 'transfer', handler);
    const res = await wrapped(makeRequest(), ctx, {});
    expect(res.status).toBe(200);
  });

  it('viewer can yachts.view but not yachts.transfer', async () => {
    const ctx = makeCtx({ permissions: makeViewerPermissions() });
    const viewRes = await withPermission('yachts', 'view', vi.fn(okHandler()))(
      makeRequest(),
      ctx,
      {},
    );
    expect(viewRes.status).toBe(200);
    const transferRes = await withPermission('yachts', 'transfer', vi.fn(okHandler()))(
      makeRequest(),
      ctx,
      {},
    );
    expect(transferRes.status).toBe(403);
  });

  it('sales_manager can yachts.transfer and memberships.manage', async () => {
    const ctx = makeCtx({ permissions: makeSalesManagerPermissions() });
    const transferRes = await withPermission('yachts', 'transfer', vi.fn(okHandler()))(
      makeRequest(),
      ctx,
      {},
    );
    expect(transferRes.status).toBe(200);
    const manageRes = await withPermission('memberships', 'manage', vi.fn(okHandler()))(
      makeRequest(),
      ctx,
      {},
    );
    expect(manageRes.status).toBe(200);
  });

  it('sales_agent can tenancies.manage but not tenancies.cancel', async () => {
    const ctx = makeCtx({ permissions: makeSalesAgentPermissions() });
    const manageRes = await withPermission('tenancies', 'manage', vi.fn(okHandler()))(
      makeRequest(),
      ctx,
      {},
    );
    expect(manageRes.status).toBe(200);
    const cancelRes = await withPermission('tenancies', 'cancel', vi.fn(okHandler()))(
      makeRequest(),
      ctx,
      {},
    );
    expect(cancelRes.status).toBe(403);
  });

  it('sales_agent cannot companies.delete', async () => {
    const ctx = makeCtx({ permissions: makeSalesAgentPermissions() });
    const res = await withPermission('companies', 'delete', vi.fn(okHandler()))(
      makeRequest(),
      ctx,
      {},
    );
    expect(res.status).toBe(403);
  });
});
