/**
 * Enterprise IdP endpoints for SmartAuth Phase 6A.
 *
 * Admin IdP management:
 * POST   /auth/enterprise/idps          — create IdP config
 * GET    /auth/enterprise/idps/:orgId    — list IdPs for org
 * GET    /auth/enterprise/idps/:orgId/:id — get single IdP
 * PUT    /auth/enterprise/idps/:orgId/:id — update IdP
 * DELETE /auth/enterprise/idps/:orgId/:id — delete IdP
 *
 * Federation callbacks:
 * POST /auth/saml/callback  — SAML assertion consumer
 * POST /auth/oidc/callback  — OIDC authorization code exchange
 *
 * Lookup:
 * GET /auth/enterprise/lookup?email=... — find IdP by email domain
 */

import type { FastifyInstance } from 'fastify';
import { BadRequestError, ForbiddenError, NotFoundError } from '../../../lib/errors.js';
import * as repo from './repository.js';
import * as userRepo from '../repository.js';
import * as jwt from '../jwt.js';
import {
  CreateIdpSchema,
  UpdateIdpSchema,
  SamlCallbackSchema,
  OidcCallbackSchema,
} from './types.js';
import type { EnterpriseIdpDoc } from './types.js';
import { randomUUID } from 'node:crypto';

export async function enterpriseRoutes(app: FastifyInstance) {
  // ── Admin: Create IdP ──────────────────────────────────────

  app.post('/auth/enterprise/idps', async req => {
    const payload = req.jwtPayload;
    if (!payload?.role || !['super_admin', 'admin'].includes(payload.role)) {
      throw new ForbiddenError('Admin access required');
    }

    const parsed = CreateIdpSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const data = parsed.data;
    if (data.protocol === 'saml' && !data.saml) {
      throw new BadRequestError('SAML config required for saml protocol');
    }
    if (data.protocol === 'oidc' && !data.oidc) {
      throw new BadRequestError('OIDC config required for oidc protocol');
    }

    const now = new Date().toISOString();
    const adminProductId = payload.productId ?? 'smartauth';
    const doc: EnterpriseIdpDoc = {
      id: `idp_${data.orgId}_${data.protocol}_${randomUUID().slice(0, 6)}`,
      orgId: data.orgId,
      productId: adminProductId,
      protocol: data.protocol,
      name: data.name,
      emailDomains: data.emailDomains.map(d => d.toLowerCase()),
      enabled: data.enabled,
      saml: data.saml,
      oidc: data.oidc,
      createdAt: now,
      updatedAt: now,
    };

    const created = await repo.create(doc);
    req.log.info(
      { idpId: created.id, orgId: data.orgId, protocol: data.protocol },
      '[auth] Enterprise IdP created'
    );
    return created;
  });

  // ── Admin: List IdPs for org ───────────────────────────────

  app.get('/auth/enterprise/idps/:orgId', async req => {
    const payload = req.jwtPayload;
    if (!payload?.role || !['super_admin', 'admin'].includes(payload.role)) {
      throw new ForbiddenError('Admin access required');
    }
    const { orgId } = req.params as { orgId: string };
    return repo.listByOrg(orgId);
  });

  // ── Admin: Get single IdP ─────────────────────────────────

  app.get('/auth/enterprise/idps/:orgId/:id', async req => {
    const payload = req.jwtPayload;
    if (!payload?.role || !['super_admin', 'admin'].includes(payload.role)) {
      throw new ForbiddenError('Admin access required');
    }
    const { orgId, id } = req.params as { orgId: string; id: string };
    const idp = await repo.getById(id, orgId);
    if (!idp) throw new NotFoundError('IdP not found');
    return idp;
  });

  // ── Admin: Update IdP ─────────────────────────────────────

  app.put('/auth/enterprise/idps/:orgId/:id', async req => {
    const payload = req.jwtPayload;
    if (!payload?.role || !['super_admin', 'admin'].includes(payload.role)) {
      throw new ForbiddenError('Admin access required');
    }
    const { orgId, id } = req.params as { orgId: string; id: string };

    const parsed = UpdateIdpSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }

    const updated = await repo.updateIdp(id, orgId, parsed.data as Partial<EnterpriseIdpDoc>);
    if (!updated) throw new NotFoundError('IdP not found');
    req.log.info({ idpId: id, orgId }, '[auth] Enterprise IdP updated');
    return updated;
  });

  // ── Admin: Delete IdP ─────────────────────────────────────

  app.delete('/auth/enterprise/idps/:orgId/:id', async req => {
    const payload = req.jwtPayload;
    if (!payload?.role || !['super_admin', 'admin'].includes(payload.role)) {
      throw new ForbiddenError('Admin access required');
    }
    const { orgId, id } = req.params as { orgId: string; id: string };
    const ok = await repo.remove(id, orgId);
    if (!ok) throw new NotFoundError('IdP not found');
    req.log.info({ idpId: id, orgId }, '[auth] Enterprise IdP deleted');
    return { message: 'IdP deleted' };
  });

  // ── Lookup IdP by email domain ────────────────────────────

  app.get('/auth/enterprise/lookup', async req => {
    const { email } = req.query as { email?: string };
    if (!email) throw new BadRequestError('email query parameter required');

    const domain = email.split('@')[1]?.toLowerCase();
    if (!domain) throw new BadRequestError('Invalid email');

    const idp = await repo.getByEmailDomain(domain);
    if (!idp) {
      return { found: false, idp: null };
    }
    // Return sanitized — no secrets
    return {
      found: true,
      idp: {
        id: idp.id,
        orgId: idp.orgId,
        protocol: idp.protocol,
        name: idp.name,
        emailDomains: idp.emailDomains,
      },
    };
  });

  // ── SAML Callback ─────────────────────────────────────────

  app.post('/auth/saml/callback', async req => {
    const parsed = SamlCallbackSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError('Invalid SAML response');
    }

    // Decode SAMLResponse (base64 XML) — simplified validation
    // In production, use @node-saml/node-saml for full signature verification
    let samlData: { email?: string; nameId?: string; attributes?: Record<string, string> };
    try {
      const xml = Buffer.from(parsed.data.SAMLResponse, 'base64').toString('utf-8');
      // Extract email from basic XML parsing (simplified — use node-saml in production)
      const emailMatch = xml.match(/<saml:NameID[^>]*>([^<]+)<\/saml:NameID>/);
      const email = emailMatch?.[1];
      if (!email) throw new Error('No NameID in SAML response');
      samlData = { email, nameId: email };
    } catch {
      throw new BadRequestError('Failed to parse SAML assertion');
    }

    const email = samlData.email!;
    const domain = email.split('@')[1]?.toLowerCase();
    if (!domain) throw new BadRequestError('Invalid email in SAML assertion');

    // Find IdP by domain
    const idp = await repo.getByEmailDomain(domain);
    if (!idp || idp.protocol !== 'saml') {
      throw new BadRequestError('No SAML IdP configured for this email domain');
    }

    // Find or create user (cross-product lookup for enterprise SSO)
    let user = await userRepo.getByEmailCrossProduct(email);
    if (!user) {
      const now = new Date().toISOString();
      user = await userRepo.create({
        id: `usr_${randomUUID()}`,
        productId: idp.productId,
        email,
        passwordHash: await userRepo.hashPassword(randomUUID()), // SSO-only — random bcrypt hash
        plan: 'free',
        role: 'user',
        displayName: samlData.attributes?.displayName ?? email.split('@')[0],
        status: 'active',
        emailVerified: true,
        lastLoginAt: now,
        createdAt: now,
        updatedAt: now,
        primaryProductId: idp.productId,
        memberships: [{ productId: idp.productId, plan: 'free', role: 'user', firstAccessAt: now }],
      });
    } else {
      await userRepo.updateLastLogin(user.id);
    }

    // Issue tokens
    const accessToken = await jwt.createAccessToken({
      sub: user.id,
      email: user.email,
      role: user.role as string,
      productId: idp.productId,
      plan: (user.plan ?? 'free') as 'free' | 'pro' | 'enterprise',
      displayName: user.displayName,
    });
    const refreshToken = await jwt.createRefreshToken({
      sub: user.id,
      productId: idp.productId,
    });

    req.log.info(
      { userId: user.id, idpId: idp.id, protocol: 'saml' },
      '[auth] Enterprise SAML login'
    );
    return {
      accessToken,
      refreshToken,
      user: { id: user.id, email: user.email, displayName: user.displayName },
    };
  });

  // ── OIDC Callback ─────────────────────────────────────────

  app.post('/auth/oidc/callback', async req => {
    const parsed = OidcCallbackSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError('Invalid OIDC callback');
    }

    // The state parameter encodes the IdP ID + orgId
    let stateData: { idpId: string; orgId: string };
    try {
      stateData = JSON.parse(Buffer.from(parsed.data.state, 'base64url').toString());
    } catch {
      throw new BadRequestError('Invalid state parameter');
    }

    const idp = await repo.getById(stateData.idpId, stateData.orgId);
    if (!idp || idp.protocol !== 'oidc' || !idp.oidc) {
      throw new BadRequestError('OIDC IdP not found or not configured');
    }

    // Exchange authorization code for tokens (simplified — use openid-client in production)
    let email: string;
    let displayName: string | undefined;
    try {
      const tokenRes = await fetch(idp.oidc.tokenUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
          grant_type: 'authorization_code',
          code: parsed.data.code,
          client_id: idp.oidc.clientId,
          client_secret: idp.oidc.clientSecret,
          redirect_uri: `${req.protocol}://${req.hostname}/auth/oidc/callback`,
        }),
      });

      if (!tokenRes.ok) throw new Error(`Token exchange failed: ${tokenRes.status}`);
      const tokenData = (await tokenRes.json()) as {
        id_token?: string;
        access_token?: string;
        email?: string;
      };

      // If id_token is returned, decode it (simplified)
      if (tokenData.id_token) {
        const idPayload = JSON.parse(
          Buffer.from(tokenData.id_token.split('.')[1], 'base64url').toString()
        );
        email = idPayload.email;
        displayName = idPayload.name;
      } else {
        throw new Error('No id_token in response');
      }
    } catch (e) {
      req.log.error({ error: (e as Error).message }, '[auth] OIDC token exchange failed');
      throw new BadRequestError('OIDC token exchange failed');
    }

    if (!email) throw new BadRequestError('No email in OIDC token');

    // Find or create user (cross-product lookup for enterprise SSO)
    let user = await userRepo.getByEmailCrossProduct(email);
    if (!user) {
      const now = new Date().toISOString();
      user = await userRepo.create({
        id: `usr_${randomUUID()}`,
        productId: idp.productId,
        email,
        passwordHash: await userRepo.hashPassword(randomUUID()), // SSO-only — random bcrypt hash
        plan: 'free',
        role: 'user',
        displayName: displayName ?? email.split('@')[0],
        status: 'active',
        emailVerified: true,
        lastLoginAt: now,
        createdAt: now,
        updatedAt: now,
        primaryProductId: idp.productId,
        memberships: [{ productId: idp.productId, plan: 'free', role: 'user', firstAccessAt: now }],
      });
    } else {
      await userRepo.updateLastLogin(user.id);
    }

    // Issue tokens
    const accessToken = await jwt.createAccessToken({
      sub: user.id,
      email: user.email,
      role: user.role as string,
      productId: idp.productId,
      plan: (user.plan ?? 'free') as 'free' | 'pro' | 'enterprise',
      displayName: user.displayName,
    });
    const refreshToken = await jwt.createRefreshToken({
      sub: user.id,
      productId: idp.productId,
    });

    req.log.info(
      { userId: user.id, idpId: idp.id, protocol: 'oidc' },
      '[auth] Enterprise OIDC login'
    );
    return {
      accessToken,
      refreshToken,
      user: { id: user.id, email: user.email, displayName: user.displayName },
    };
  });
}
