import { randomUUID } from 'node:crypto';
import type { FastifyInstance } from 'fastify';
import { BadRequestError, ForbiddenError } from '../../lib/errors.js';
import * as orgRepo from '../orgs/repository.js';
import {
  CreateScimConnectorSchema,
  RecordScimEventSchema,
  RecordScimGroupSyncSchema,
  RecordScimUserSyncSchema,
  ScimConnectorDoc,
  ScimGroupSyncDoc,
  ScimProvisioningEventDoc,
  ScimUserSyncDoc,
  UpdateScimConnectorSchema,
} from './types.js';
import * as repo from './repository.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',
  };
}

function validationError(message: string): never {
  throw new BadRequestError(message);
}

export async function scimRoutes(app: FastifyInstance) {
  app.get('/scim/connectors/:orgId', async req => {
    requireAdmin(req);
    const { orgId } = req.params as { orgId: string };
    return repo.listConnectors(orgId);
  });

  app.post('/scim/connectors', async req => {
    const access = requireAdmin(req);
    const parsed = CreateScimConnectorSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    await orgRepo.getOrganization(parsed.data.orgId, access.productId);

    const now = new Date().toISOString();
    const connector: ScimConnectorDoc = {
      id: `scim_${randomUUID()}`,
      orgId: parsed.data.orgId,
      productId: access.productId,
      idpId: parsed.data.idpId,
      name: parsed.data.name,
      baseUrl: parsed.data.baseUrl,
      authMode: parsed.data.authMode,
      status: 'active',
      metadata: parsed.data.metadata,
      createdBy: access.userId,
      createdAt: now,
      updatedAt: now,
    };
    return repo.createConnector(connector);
  });

  app.get('/scim/connectors/:orgId/:id', async req => {
    requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    return repo.getConnector(id, orgId);
  });

  app.patch('/scim/connectors/:orgId/:id', async req => {
    requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    const parsed = UpdateScimConnectorSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    return repo.updateConnector(id, orgId, parsed.data);
  });

  app.get('/scim/connectors/:orgId/:id/users', async req => {
    requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    await repo.getConnector(id, orgId);
    return repo.listUserSync(id);
  });

  app.post('/scim/connectors/:orgId/:id/users', async req => {
    const access = requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    await repo.getConnector(id, orgId);
    const parsed = RecordScimUserSyncSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const doc: ScimUserSyncDoc = {
      id: `${id}:user:${parsed.data.externalUserId}`,
      orgId,
      connectorId: id,
      productId: access.productId,
      externalUserId: parsed.data.externalUserId,
      userId: parsed.data.userId,
      email: parsed.data.email,
      status: parsed.data.status,
      lastSyncedAt: new Date().toISOString(),
      errorMessage: parsed.data.errorMessage,
      metadata: parsed.data.metadata,
    };
    return repo.upsertUserSync(doc);
  });

  app.get('/scim/connectors/:orgId/:id/groups', async req => {
    requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    await repo.getConnector(id, orgId);
    return repo.listGroupSync(id);
  });

  app.post('/scim/connectors/:orgId/:id/groups', async req => {
    const access = requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    await repo.getConnector(id, orgId);
    const parsed = RecordScimGroupSyncSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const doc: ScimGroupSyncDoc = {
      id: `${id}:group:${parsed.data.externalGroupId}`,
      orgId,
      connectorId: id,
      productId: access.productId,
      externalGroupId: parsed.data.externalGroupId,
      workspaceId: parsed.data.workspaceId,
      displayName: parsed.data.displayName,
      memberCount: parsed.data.memberCount,
      status: parsed.data.status,
      lastSyncedAt: new Date().toISOString(),
      errorMessage: parsed.data.errorMessage,
      metadata: parsed.data.metadata,
    };
    return repo.upsertGroupSync(doc);
  });

  app.get('/scim/connectors/:orgId/:id/events', async req => {
    requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    await repo.getConnector(id, orgId);
    return repo.listEvents(id);
  });

  app.post('/scim/connectors/:orgId/:id/events', async req => {
    const access = requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    await repo.getConnector(id, orgId);
    const parsed = RecordScimEventSchema.safeParse(req.body);
    if (!parsed.success) {
      validationError(parsed.error.issues.map(issue => issue.message).join('; '));
    }
    const doc: ScimProvisioningEventDoc = {
      id: `scimevt_${randomUUID()}`,
      orgId,
      connectorId: id,
      productId: access.productId,
      entityType: parsed.data.entityType,
      entityId: parsed.data.entityId,
      action: parsed.data.action,
      status: parsed.data.status,
      message: parsed.data.message,
      createdAt: new Date().toISOString(),
    };
    return repo.createEvent(doc);
  });

  // ── Connector stats ────────────────────────────────────
  app.get('/scim/connectors/:orgId/:id/stats', async req => {
    requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    await repo.getConnector(id, orgId);
    return repo.getConnectorStats(id);
  });

  // ── Delete connector (paused only) ─────────────────────
  app.delete('/scim/connectors/:orgId/:id', async req => {
    requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    const connector = await repo.getConnector(id, orgId);
    if (connector.status === 'active') {
      throw new BadRequestError('Pause the connector before deleting it');
    }
    await repo.deleteConnector(id, orgId);
    return { deleted: true };
  });

  // ── Pause connector ────────────────────────────────────
  app.post('/scim/connectors/:orgId/:id/pause', async req => {
    requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    const connector = await repo.getConnector(id, orgId);
    if (connector.status !== 'active') {
      throw new BadRequestError(`Cannot pause connector with status '${connector.status}'`);
    }
    return repo.updateConnector(id, orgId, { status: 'paused' });
  });

  // ── Resume connector ───────────────────────────────────
  app.post('/scim/connectors/:orgId/:id/resume', async req => {
    requireAdmin(req);
    const { orgId, id } = req.params as { orgId: string; id: string };
    const connector = await repo.getConnector(id, orgId);
    if (connector.status !== 'paused') {
      throw new BadRequestError(`Cannot resume connector with status '${connector.status}'`);
    }
    return repo.updateConnector(id, orgId, { status: 'active' });
  });
}
