/**
 * Microsoft Entra VerifiedID webhook/callback handler
 * Handles status updates from Entra VerifiedID service
 */

import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { getEnv } from '@the-order/shared';
import { updateVerifiableCredential, getVerifiableCredentialById } from '@the-order/database';
import { getEventBus, CredentialEvents } from '@the-order/events';
import {
  entraWebhooksReceived,
  entraWebhookProcessingDuration,
  entraWebhookErrors,
} from '@the-order/monitoring';
import { createLogger } from '@the-order/shared';

const logger = createLogger('entra-webhooks');

export interface EntraWebhookPayload {
  requestId: string;
  requestStatus: 'request_created' | 'request_retrieved' | 'issuance_successful' | 'issuance_failed';
  state?: string;
  code?: string;
  error?: {
    code: string;
    message: string;
  };
  credential?: {
    id: string;
    type: string[];
    issuer: string;
    issuanceDate: string;
    expirationDate?: string;
    credentialSubject: Record<string, unknown>;
    proof: {
      type: string;
      created: string;
      proofPurpose: string;
      verificationMethod: string;
      jws: string;
    };
  };
}

/**
 * Validate webhook signature (if Entra provides one)
 * Note: Entra VerifiedID may not sign webhooks, so this is a placeholder
 */
function validateWebhookSignature(_payload: EntraWebhookPayload, _signature?: string): boolean {
  // TODO: Implement signature validation if Entra provides webhook signing
  // For now, we rely on HTTPS and callback URL validation
  return true;
}

/**
 * Process Entra webhook payload
 */
async function processWebhook(payload: EntraWebhookPayload): Promise<void> {
  const startTime = Date.now();
  const { requestId, requestStatus, error, credential } = payload;

  try {
    logger.info('Processing Entra webhook', { requestId, requestStatus });

    // Update metrics
    entraWebhooksReceived.inc({ event_type: requestStatus, status: 'received' });

    // Find credential in database
    const dbCredential = await getVerifiableCredentialById(requestId);
    if (!dbCredential) {
      logger.warn('Credential not found in database', { requestId });
      entraWebhookErrors.inc({ event_type: requestStatus, error_type: 'not_found' });
      return;
    }

    // Update credential status based on webhook
    if (requestStatus === 'issuance_successful' && credential) {
      // Update credential with full credential data
      await updateVerifiableCredential(requestId, {
        status: 'issued',
        credential_data: credential,
        issued_at: new Date(credential.issuanceDate),
        expires_at: credential.expirationDate ? new Date(credential.expirationDate) : undefined,
      });

      // Publish credential issued event
      await getEventBus().publish(CredentialEvents.ISSUED, {
        credentialId: requestId,
        issuerDid: credential.issuer,
        subjectDid: dbCredential.subject_did,
        credentialType: credential.type,
        issuedAt: credential.issuanceDate,
      });

      logger.info('Credential issued successfully', { requestId });
      entraWebhooksReceived.inc({ event_type: requestStatus, status: 'processed' });
    } else if (requestStatus === 'issuance_failed') {
      // Update credential status to failed
      await updateVerifiableCredential(requestId, {
        status: 'failed',
        error: error?.message || 'Issuance failed',
      });

      // Publish credential issuance failed event
      await getEventBus().publish('credential.issuance.failed', {
        credentialId: requestId,
        error: error?.message || 'Unknown error',
      });

      logger.error('Credential issuance failed', { requestId, error });
      entraWebhookErrors.inc({ event_type: requestStatus, error_type: 'issuance_failed' });
    } else if (requestStatus === 'request_retrieved') {
      // User has retrieved the issuance request
      await updateVerifiableCredential(requestId, {
        status: 'pending',
      });

      logger.info('Issuance request retrieved by user', { requestId });
      entraWebhooksReceived.inc({ event_type: requestStatus, status: 'processed' });
    }

    // Record processing duration
    const duration = (Date.now() - startTime) / 1000;
    entraWebhookProcessingDuration.observe({ event_type: requestStatus }, duration);
  } catch (error) {
    logger.error('Error processing Entra webhook', { requestId, error });
    entraWebhookErrors.inc({ event_type: requestStatus, error_type: 'processing_error' });
    entraWebhookProcessingDuration.observe({ event_type: requestStatus }, (Date.now() - startTime) / 1000);
    throw error;
  }
}

/**
 * Register Entra webhook routes
 */
export async function registerEntraWebhookRoutes(server: FastifyInstance): Promise<void> {
  const env = getEnv();

  // Webhook endpoint for Entra VerifiedID callbacks
  server.post(
    '/vc/entra/webhook',
    {
      schema: {
        description: 'Webhook endpoint for Entra VerifiedID status updates',
        tags: ['credentials', 'entra', 'webhooks'],
        body: {
          type: 'object',
          required: ['requestId', 'requestStatus'],
          properties: {
            requestId: { type: 'string' },
            requestStatus: {
              type: 'string',
              enum: ['request_created', 'request_retrieved', 'issuance_successful', 'issuance_failed'],
            },
            state: { type: 'string' },
            code: { type: 'string' },
            error: {
              type: 'object',
              properties: {
                code: { type: 'string' },
                message: { type: 'string' },
              },
            },
            credential: {
              type: 'object',
              properties: {
                id: { type: 'string' },
                type: { type: 'array', items: { type: 'string' } },
                issuer: { type: 'string' },
                issuanceDate: { type: 'string' },
                expirationDate: { type: 'string' },
                credentialSubject: { type: 'object' },
                proof: { type: 'object' },
              },
            },
          },
        },
        response: {
          200: {
            type: 'object',
            properties: {
              received: { type: 'boolean' },
            },
          },
        },
      },
    },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const payload = request.body as EntraWebhookPayload;

      try {
        // Optional: Validate webhook signature if provided
        const signature = request.headers['x-entra-signature'] as string | undefined;
        if (!validateWebhookSignature(payload, signature)) {
          logger.warn('Invalid webhook signature', { requestId: payload.requestId });
          return reply.status(401).send({ error: 'Invalid signature' });
        }

        // Process webhook asynchronously
        processWebhook(payload).catch((error) => {
          logger.error('Async webhook processing failed', { error, requestId: payload.requestId });
        });

        // Return immediately to acknowledge receipt
        return reply.status(200).send({ received: true });
      } catch (error) {
        logger.error('Error handling Entra webhook', { error });
        return reply.status(500).send({
          error: 'Failed to process webhook',
          message: error instanceof Error ? error.message : String(error),
        });
      }
    }
  );

  // Manual status check endpoint (for polling fallback)
  server.get(
    '/vc/entra/status/:requestId',
    {
      schema: {
        description: 'Check Entra credential issuance status',
        tags: ['credentials', 'entra'],
        params: {
          type: 'object',
          properties: {
            requestId: { type: 'string' },
          },
        },
        response: {
          200: {
            type: 'object',
            properties: {
              requestId: { type: 'string' },
              status: { type: 'string' },
              credential: { type: 'object' },
            },
          },
        },
      },
    },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const { requestId } = request.params as { requestId: string };

      try {
        const credential = await getVerifiableCredentialById(requestId);
        if (!credential) {
          return reply.status(404).send({ error: 'Credential not found' });
        }

        return reply.status(200).send({
          requestId,
          status: credential.status || 'pending',
          credential: credential.credential_data || null,
        });
      } catch (error) {
        logger.error('Error checking credential status', { error, requestId });
        return reply.status(500).send({
          error: 'Failed to check status',
          message: error instanceof Error ? error.message : String(error),
        });
      }
    }
  );

  logger.info('Entra webhook routes registered');
}

