import { randomUUID } from 'node:crypto';
import type { FastifyInstance } from 'fastify';
import { BadRequestError, ForbiddenError } from '../../lib/errors.js';
import * as repo from './repository.js';
import {
  CreateMembershipSchema,
  CreateOrganizationSchema,
  CreateWorkspaceSchema,
  ListMembershipsQuerySchema,
  ListOrganizationsQuerySchema,
  MembershipDoc,
  OrganizationDoc,
  UpdateMembershipSchema,
  UpdateOrganizationSchema,
  UpdateWorkspaceSchema,
  WorkspaceDoc,
  hasPermission,
  canManageRole,
  type Permission,
} from './types.js';

function requireAdmin(req: { jwtPayload?: { sub?: string; role?: string; productId?: string } }): {
  userId: string;
  productId: string;
} {
  const payload = req.jwtPayload;
  if (!payload?.sub) {
    throw new ForbiddenError('Authentication required');
  }
  if (!payload.role || !['super_admin', 'admin'].includes(payload.role)) {
    throw new ForbiddenError('Admin access required');
  }
  return {
    userId: payload.sub,
    productId: payload.productId ?? process.env.DEFAULT_PRODUCT_ID ?? 'lysnrai',
  };
}

export async function orgRoutes(app: FastifyInstance) {
  app.get('/orgs', async req => {
    const access = requireAdmin(req);
    const parsed = ListOrganizationsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.listOrganizations(access.productId, parsed.data);
  });

  app.post('/orgs', async req => {
    const access = requireAdmin(req);
    const parsed = CreateOrganizationSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    const now = new Date().toISOString();
    const org: OrganizationDoc = {
      id: `org_${randomUUID()}`,
      productId: access.productId,
      name: parsed.data.name,
      slug: parsed.data.slug,
      status: 'active',
      ownerUserId: parsed.data.ownerUserId ?? access.userId,
      metadata: parsed.data.metadata,
      createdAt: now,
      updatedAt: now,
    };

    const created = await repo.createOrganization(org);
    const ownerMembership: MembershipDoc = {
      id: `mbr_${created.id}_${created.ownerUserId}_org`,
      orgId: created.id,
      productId: access.productId,
      scope: 'org',
      userId: created.ownerUserId,
      role: 'owner',
      status: 'active',
      createdAt: now,
      updatedAt: now,
    };
    await repo.createMembership(ownerMembership);

    return created;
  });

  app.get('/orgs/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.getOrganization(id, access.productId);
  });

  app.patch('/orgs/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = UpdateOrganizationSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.updateOrganization(id, access.productId, parsed.data);
  });

  app.get('/orgs/:id/workspaces', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    return repo.listWorkspaces(id);
  });

  app.post('/orgs/:id/workspaces', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const org = await repo.getOrganization(id, access.productId);
    const parsed = CreateWorkspaceSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    const now = new Date().toISOString();
    const workspace: WorkspaceDoc = {
      id: `ws_${randomUUID()}`,
      orgId: org.id,
      productId: access.productId,
      name: parsed.data.name,
      slug: parsed.data.slug,
      status: 'active',
      description: parsed.data.description,
      metadata: parsed.data.metadata,
      createdAt: now,
      updatedAt: now,
    };

    return repo.createWorkspace(workspace);
  });

  app.patch('/orgs/:id/workspaces/:workspaceId', async req => {
    requireAdmin(req);
    const { id, workspaceId } = req.params as { id: string; workspaceId: string };
    const parsed = UpdateWorkspaceSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.updateWorkspace(workspaceId, id, parsed.data);
  });

  app.get('/orgs/:id/memberships', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = ListMembershipsQuerySchema.safeParse(req.query);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.listMemberships(id, parsed.data);
  });

  app.post('/orgs/:id/memberships', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const parsed = CreateMembershipSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    if (parsed.data.scope === 'workspace' && !parsed.data.workspaceId) {
      throw new BadRequestError('workspaceId is required for workspace-scoped memberships');
    }

    await repo.getOrganization(id, access.productId);
    if (parsed.data.workspaceId) {
      await repo.getWorkspace(parsed.data.workspaceId, id);
    }

    const now = new Date().toISOString();
    const membership: MembershipDoc = {
      id: `mbr_${id}_${parsed.data.userId}_${parsed.data.workspaceId ?? 'org'}`,
      orgId: id,
      productId: access.productId,
      scope: parsed.data.scope,
      workspaceId: parsed.data.workspaceId,
      userId: parsed.data.userId,
      role: parsed.data.role,
      status: 'active',
      invitedBy: parsed.data.invitedBy ?? access.userId,
      createdAt: now,
      updatedAt: now,
    };
    return repo.createMembership(membership);
  });

  app.patch('/orgs/:id/memberships/:membershipId', async req => {
    const access = requireAdmin(req);
    const { id, membershipId } = req.params as { id: string; membershipId: string };
    const parsed = UpdateMembershipSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(issue => issue.message).join('; '));
    }

    // RBAC: check actor can manage the target role
    if (parsed.data.role) {
      const actorMembership = await repo.getUserMembership(id, access.userId);
      const targetMembership = await repo.getMembership(membershipId, id);
      const actorRole = actorMembership?.role ?? 'viewer';
      if (!canManageRole(actorRole, targetMembership.role)) {
        throw new ForbiddenError('Cannot modify a member with equal or higher role');
      }
      if (!canManageRole(actorRole, parsed.data.role)) {
        throw new ForbiddenError('Cannot assign a role equal to or higher than your own');
      }
    }

    return repo.updateMembership(membershipId, id, parsed.data);
  });

  // ── Delete organization (owner only) ────────────────────
  app.delete('/orgs/:id', async req => {
    const access = requireAdmin(req);
    const { id } = req.params as { id: string };
    const org = await repo.getOrganization(id, access.productId);
    if (org.ownerUserId !== access.userId) {
      throw new ForbiddenError('Only the organization owner can delete it');
    }
    await repo.deleteOrganization(id, access.productId);
    return { deleted: true };
  });

  // ── Delete workspace ────────────────────────────────────
  app.delete('/orgs/:id/workspaces/:workspaceId', async req => {
    const access = requireAdmin(req);
    const { id, workspaceId } = req.params as { id: string; workspaceId: string };
    await repo.getOrganization(id, access.productId);
    await repo.getWorkspace(workspaceId, id);
    await repo.deleteWorkspace(workspaceId, id);
    return { deleted: true };
  });

  // ── Remove membership ──────────────────────────────────
  app.delete('/orgs/:id/memberships/:membershipId', async req => {
    const access = requireAdmin(req);
    const { id, membershipId } = req.params as { id: string; membershipId: string };
    const targetMembership = await repo.getMembership(membershipId, id);

    // Cannot remove an owner
    if (targetMembership.role === 'owner') {
      throw new ForbiddenError('Cannot remove the organization owner');
    }

    // RBAC: actor must outrank target
    const actorMembership = await repo.getUserMembership(id, access.userId);
    const actorRole = actorMembership?.role ?? 'viewer';
    if (!canManageRole(actorRole, targetMembership.role)) {
      throw new ForbiddenError('Cannot remove a member with equal or higher role');
    }

    await repo.deleteMembership(membershipId, id);
    return { deleted: true };
  });

  // ── Check permissions for a user ───────────────────────
  app.get('/orgs/:id/permissions', async req => {
    requireAdmin(req);
    const { id } = req.params as { id: string };
    const userId = (req.query as { userId?: string }).userId;
    if (!userId) throw new BadRequestError('userId query parameter required');

    const membership = await repo.getUserMembership(id, userId);
    if (!membership) {
      return { userId, orgId: id, role: null, permissions: [] };
    }

    const permissions = (
      [
        'org:read',
        'org:update',
        'org:delete',
        'workspace:create',
        'workspace:read',
        'workspace:update',
        'workspace:archive',
        'member:invite',
        'member:read',
        'member:update_role',
        'member:remove',
      ] as Permission[]
    ).filter(p => hasPermission(membership.role, p));

    return {
      userId,
      orgId: id,
      role: membership.role,
      permissions,
    };
  });
}
