/**
 * Microsoft Entra VerifiedID integration for Identity Service
 */

import { FastifyInstance } from 'fastify';
import {
  EntraVerifiedIDClient,
  VerifiableCredentialRequest,
} from '@the-order/auth';
import { EnhancedEntraVerifiedIDClient } from '@the-order/auth';
import { EIDASToEntraBridge } from '@the-order/auth';
import { getEnv } from '@the-order/shared';
import { createVerifiableCredential } from '@the-order/database';
import {
  entraApiRequests,
  entraApiRequestDuration,
  entraApiErrors,
  entraCredentialsIssued,
  entraIssuanceDuration,
  entraIssuanceRetries,
  entraCredentialsVerified,
  entraVerificationDuration,
  entraActiveRequests,
} from '@the-order/monitoring';
import { registerEntraRateLimit } from '@the-order/shared';

/**
 * Initialize Enhanced Entra VerifiedID client with multi-manifest support
 */
export function createEntraClient(): EnhancedEntraVerifiedIDClient | null {
  const env = getEnv();
  
  if (!env.ENTRA_TENANT_ID || !env.ENTRA_CLIENT_ID || !env.ENTRA_CLIENT_SECRET) {
    return null;
  }

  // Parse manifests from environment variable if provided
  let manifests: Record<string, string> | undefined;
  if (env.ENTRA_MANIFESTS) {
    try {
      manifests = JSON.parse(env.ENTRA_MANIFESTS);
    } catch (error) {
      console.warn('Failed to parse ENTRA_MANIFESTS, using default manifest only', error);
    }
  }

  // Add default manifest if provided
  if (env.ENTRA_CREDENTIAL_MANIFEST_ID) {
    manifests = manifests || {};
    manifests['default'] = env.ENTRA_CREDENTIAL_MANIFEST_ID;
  }

  return new EnhancedEntraVerifiedIDClient({
    tenantId: env.ENTRA_TENANT_ID,
    clientId: env.ENTRA_CLIENT_ID,
    clientSecret: env.ENTRA_CLIENT_SECRET,
    credentialManifestId: env.ENTRA_CREDENTIAL_MANIFEST_ID,
    manifests,
    logoUri: env.ENTRA_CREDENTIAL_LOGO_URI,
    backgroundColor: env.ENTRA_CREDENTIAL_BG_COLOR,
    textColor: env.ENTRA_CREDENTIAL_TEXT_COLOR,
  }, {
    maxRetries: 3,
    initialDelayMs: 1000,
    maxDelayMs: 10000,
    backoffMultiplier: 2,
  });
}

/**
 * Initialize eIDAS to Entra bridge
 */
export function createEIDASToEntraBridge(): EIDASToEntraBridge | null {
  const env = getEnv();
  
  if (
    !env.ENTRA_TENANT_ID ||
    !env.ENTRA_CLIENT_ID ||
    !env.ENTRA_CLIENT_SECRET ||
    !env.EIDAS_PROVIDER_URL ||
    !env.EIDAS_API_KEY
  ) {
    return null;
  }

  return new EIDASToEntraBridge({
    entraVerifiedID: {
      tenantId: env.ENTRA_TENANT_ID,
      clientId: env.ENTRA_CLIENT_ID,
      clientSecret: env.ENTRA_CLIENT_SECRET,
      credentialManifestId: env.ENTRA_CREDENTIAL_MANIFEST_ID || '',
    },
    eidas: {
      providerUrl: env.EIDAS_PROVIDER_URL,
      apiKey: env.EIDAS_API_KEY,
    },
    logicApps: env.AZURE_LOGIC_APPS_WORKFLOW_URL
      ? {
          workflowUrl: env.AZURE_LOGIC_APPS_WORKFLOW_URL,
          accessKey: env.AZURE_LOGIC_APPS_ACCESS_KEY,
          managedIdentityClientId: env.AZURE_LOGIC_APPS_MANAGED_IDENTITY_CLIENT_ID,
        }
      : undefined,
  });
}

/**
 * Register Entra VerifiedID routes
 */
export async function registerEntraRoutes(server: FastifyInstance): Promise<void> {
  const entraClient = createEntraClient();
  const eidasBridge = createEIDASToEntraBridge();

  if (!entraClient) {
    server.log.warn('Microsoft Entra VerifiedID not configured - routes will not be available');
    return;
  }

  // Register Entra-specific rate limiting
  await registerEntraRateLimit(server);

  // Issue credential via Entra VerifiedID
  server.post(
    '/vc/issue/entra',
    {
      schema: {
        description: 'Issue verifiable credential via Microsoft Entra VerifiedID',
        tags: ['credentials', 'entra'],
        body: {
          type: 'object',
          required: ['claims'],
          properties: {
            claims: {
              type: 'object',
              description: 'Credential claims (values can be string, number, boolean, or null)',
              additionalProperties: {
                oneOf: [
                  { type: 'string' },
                  { type: 'number' },
                  { type: 'boolean' },
                  { type: 'null' },
                ],
              },
            },
            pin: {
              type: 'string',
              description: 'Optional PIN for credential issuance',
            },
            callbackUrl: {
              type: 'string',
              format: 'uri',
              description: 'Optional callback URL for issuance status',
            },
          },
        },
        response: {
          200: {
            type: 'object',
            properties: {
              requestId: { type: 'string' },
              url: { type: 'string' },
              qrCode: { type: 'string' },
            },
          },
        },
      },
    },
    async (request, reply) => {
      const body = request.body as VerifiableCredentialRequest & { manifestName?: string };
      const startTime = Date.now();
      entraActiveRequests.inc({ operation: 'issueCredential' });
      
      try {
        entraApiRequests.inc({ operation: 'issueCredential', status: 'attempt' });
        
        const credentialResponse = await entraClient.issueCredential(body);
        
        // Record success metrics
        const duration = (Date.now() - startTime) / 1000;
        entraIssuanceDuration.observe({ manifest_name: body.manifestName || 'default' }, duration);
        entraApiRequestDuration.observe({ operation: 'issueCredential' }, duration);
        entraCredentialsIssued.inc({ manifest_name: body.manifestName || 'default', status: 'success' });
        entraApiRequests.inc({ operation: 'issueCredential', status: 'success' });
        
        return reply.status(200).send(credentialResponse);
      } catch (error) {
        // Record error metrics
        const duration = (Date.now() - startTime) / 1000;
        entraApiRequestDuration.observe({ operation: 'issueCredential' }, duration);
        entraApiErrors.inc({ 
          operation: 'issueCredential', 
          error_type: error instanceof Error ? error.constructor.name : 'Unknown',
          status_code: (error as any)?.statusCode || 0,
        });
        entraCredentialsIssued.inc({ manifest_name: body.manifestName || 'default', status: 'error' });
        entraApiRequests.inc({ operation: 'issueCredential', status: 'error' });
        
        return reply.status(500).send({
          error: 'Failed to issue credential',
          message: error instanceof Error ? error.message : String(error),
        });
      } finally {
        entraActiveRequests.dec({ operation: 'issueCredential' });
      }
    }
  );

  // Verify credential via Entra VerifiedID
  server.post(
    '/vc/verify/entra',
    {
      schema: {
        description: 'Verify verifiable credential via Microsoft Entra VerifiedID',
        tags: ['credentials', 'entra'],
        body: {
          type: 'object',
          required: ['credential'],
          properties: {
            credential: {
              type: 'object',
              description: 'Verifiable credential to verify',
            },
          },
        },
        response: {
          200: {
            type: 'object',
            properties: {
              verified: { type: 'boolean' },
            },
          },
        },
      },
    },
    async (request, reply) => {
      const body = request.body as { credential: unknown };
      const startTime = Date.now();
      entraActiveRequests.inc({ operation: 'verifyCredential' });
      
      try {
        entraApiRequests.inc({ operation: 'verifyCredential', status: 'attempt' });
        
        const verified = await entraClient.verifyCredential(
          body.credential as Parameters<typeof entraClient.verifyCredential>[0]
        );
        
        // Record success metrics
        const duration = (Date.now() - startTime) / 1000;
        entraVerificationDuration.observe(duration);
        entraApiRequestDuration.observe({ operation: 'verifyCredential' }, duration);
        entraCredentialsVerified.inc({ result: verified ? 'verified' : 'not_verified' });
        entraApiRequests.inc({ operation: 'verifyCredential', status: 'success' });
        
        return reply.status(200).send({ verified });
      } catch (error) {
        // Record error metrics
        const duration = (Date.now() - startTime) / 1000;
        entraApiRequestDuration.observe({ operation: 'verifyCredential' }, duration);
        entraApiErrors.inc({ 
          operation: 'verifyCredential', 
          error_type: error instanceof Error ? error.constructor.name : 'Unknown',
          status_code: (error as any)?.statusCode || 0,
        });
        entraCredentialsVerified.inc({ result: 'error' });
        entraApiRequests.inc({ operation: 'verifyCredential', status: 'error' });
        
        return reply.status(500).send({
          error: 'Failed to verify credential',
          message: error instanceof Error ? error.message : String(error),
        });
      } finally {
        entraActiveRequests.dec({ operation: 'verifyCredential' });
      }
    }
  );

  // eIDAS verification with Entra VerifiedID issuance
  if (eidasBridge) {
    server.post(
      '/eidas/verify-and-issue',
      {
        schema: {
          description: 'Verify eIDAS signature and issue credential via Entra VerifiedID',
          tags: ['eidas', 'entra'],
          body: {
            type: 'object',
            required: ['document', 'userId', 'userEmail'],
            properties: {
            document: {
              type: 'string',
              description: 'Document to verify and sign (base64-encoded string or buffer will be auto-encoded)',
            },
              userId: {
                type: 'string',
                description: 'User ID',
              },
              userEmail: {
                type: 'string',
                format: 'email',
                description: 'User email',
              },
              pin: {
                type: 'string',
                description: 'Optional PIN for credential issuance',
              },
            },
          },
          response: {
            200: {
              type: 'object',
              properties: {
                verified: { type: 'boolean' },
                credentialRequest: {
                  type: 'object',
                  properties: {
                    requestId: { type: 'string' },
                    url: { type: 'string' },
                    qrCode: { type: 'string' },
                  },
                },
              },
            },
          },
        },
      },
      async (request, reply) => {
        const body = request.body as {
          document: string;
          userId: string;
          userEmail: string;
          pin?: string;
        };
        
        try {
          const result = await eidasBridge.verifyAndIssue(
            body.document,
            body.userId,
            body.userEmail,
            body.pin
          );

          if (result.verified && result.credentialRequest) {
            // Save credential request to database
            await createVerifiableCredential({
              credential_id: result.credentialRequest.requestId,
              issuer_did: `did:web:${getEnv().ENTRA_TENANT_ID}.verifiedid.msidentity.com`,
              subject_did: body.userId,
              credential_type: ['VerifiableCredential', 'EntraVerifiedIDCredential'],
              credential_subject: {
                email: body.userEmail,
                userId: body.userId,
                eidasVerified: true,
              },
              issuance_date: new Date(),
            });
          }
          
          return reply.status(200).send(result);
        } catch (error) {
          return reply.status(500).send({
            error: 'Failed to verify eIDAS and issue credential',
            message: error instanceof Error ? error.message : String(error),
          });
        }
      }
    );
  }
}

