/**
 * eResidency Application Flow
 * Handles applicant flow: account creation, KYC, sanctions screening, risk assessment, and issuance
 */

import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import { randomUUID } from 'crypto';
import { KMSClient } from '@the-order/crypto';
import {
  ApplicationStatus,
  LevelOfAssurance,
  EvidenceType,
  EvidenceResult,
  type eResidentCredential,
} from '@the-order/schemas';
import {
  createVerifiableCredential,
  logCredentialAction,
  createEResidencyApplication,
  getEResidencyApplicationById,
  updateEResidencyApplication,
  revokeCredential,
  query,
} from '@the-order/database';
import { getEnv, authenticateJWT, requireRole } from '@the-order/shared';
import { getEventBus } from '@the-order/events';
import { getNotificationService } from '@the-order/notifications';
import { VeriffKYCProvider } from './kyc-integration';
import { ComplyAdvantageSanctionsProvider } from './sanctions-screening';
import { getRiskAssessmentEngine } from './risk-assessment';

export interface ApplicationFlowConfig {
  kmsClient: KMSClient;
  kycProvider: 'veriff' | 'other';
  sanctionsProvider: 'complyadvantage' | 'other';
}

/**
 * Register application flow endpoints
 */
export async function registerApplicationFlowRoutes(
  server: FastifyInstance,
  config: ApplicationFlowConfig
): Promise<void> {
  const env = getEnv();
  const issuerDid = env.VC_ISSUER_DID || (env.VC_ISSUER_DOMAIN ? `did:web:${env.VC_ISSUER_DOMAIN}` : undefined);

  // Create application
  server.post(
    '/apply',
    {
      schema: {
        body: {
          type: 'object',
          required: ['email', 'givenName', 'familyName'],
          properties: {
            email: { type: 'string', format: 'email' },
            givenName: { type: 'string' },
            familyName: { type: 'string' },
            dateOfBirth: { type: 'string', format: 'date' },
            nationality: { type: 'string' },
            phone: { type: 'string' },
            address: {
              type: 'object',
              properties: {
                street: { type: 'string' },
                city: { type: 'string' },
                region: { type: 'string' },
                postalCode: { type: 'string' },
                country: { type: 'string' },
              },
            },
            deviceFingerprint: { type: 'string' },
          },
        },
        description: 'Create eResidency application',
        tags: ['application'],
      },
    },
      async (request: FastifyRequest, reply: FastifyReply) => {
      const body = request.body as {
        email: string;
        givenName: string;
        familyName: string;
        dateOfBirth?: string;
        nationality?: string;
        phone?: string;
        address?: {
          street?: string;
          city?: string;
          region?: string;
          postalCode?: string;
          country?: string;
        };
        deviceFingerprint?: string;
      };

      try {
        // Create application record
        const application = await createEResidencyApplication({
          email: body.email,
          givenName: body.givenName,
          familyName: body.familyName,
          dateOfBirth: body.dateOfBirth,
          nationality: body.nationality,
          phone: body.phone,
          address: body.address,
          deviceFingerprint: body.deviceFingerprint,
          status: ApplicationStatus.DRAFT,
        });

        // Initiate KYC flow
        const env = getEnv();
        if (env.VERIFF_API_KEY && env.VERIFF_API_URL && env.VERIFF_WEBHOOK_SECRET) {
          const kycProvider = new VeriffKYCProvider({
            apiKey: env.VERIFF_API_KEY,
            apiUrl: env.VERIFF_API_URL,
            webhookSecret: env.VERIFF_WEBHOOK_SECRET,
          });

          const kycSession = await kycProvider.createSession(application.id, {
            email: body.email,
            givenName: body.givenName,
            familyName: body.familyName,
            dateOfBirth: body.dateOfBirth,
            nationality: body.nationality,
          });

          // Update application with KYC session ID
          await updateEResidencyApplication(application.id, {
            kycStatus: 'pending',
          });

          return reply.send({
            applicationId: application.id,
            status: ApplicationStatus.DRAFT,
            nextStep: 'kyc',
            kycUrl: kycSession.verificationUrl,
            kycSessionId: kycSession.sessionId,
          });
        } else {
          // KYC not configured - return application ID for manual processing
          return reply.send({
            applicationId: application.id,
            status: ApplicationStatus.DRAFT,
            nextStep: 'kyc',
            message: 'KYC provider not configured',
          });
        }
      } catch (error) {
        return reply.code(500).send({
          error: 'Failed to create application',
          message: error instanceof Error ? error.message : String(error),
        });
      }
    }
  );

  // KYC callback (webhook from provider)
  server.post(
    '/kyc/callback',
    {
      schema: {
        body: {
          type: 'object',
          required: ['applicationId', 'status', 'results'],
          properties: {
            applicationId: { type: 'string' },
            status: { type: 'string', enum: ['approved', 'rejected', 'pending'] },
            results: {
              type: 'object',
              properties: {
                documentVerification: { type: 'object' },
                livenessCheck: { type: 'object' },
                riskScore: { type: 'number' },
              },
            },
          },
        },
        description: 'KYC provider callback',
        tags: ['application'],
      },
    },
      async (request: FastifyRequest, reply: FastifyReply) => {
      const body = request.body as {
        applicationId?: string;
        sessionId?: string;
        status: string;
        results?: {
          documentVerification?: unknown;
          livenessCheck?: unknown;
          riskScore?: number;
        };
      };

      try {
        // Verify webhook signature if configured
        const env = getEnv();
        if (env.VERIFF_WEBHOOK_SECRET && env.VERIFF_API_KEY && env.VERIFF_API_URL && request.headers['x-signature']) {
          const kycProvider = new VeriffKYCProvider({
            apiKey: env.VERIFF_API_KEY,
            apiUrl: env.VERIFF_API_URL,
            webhookSecret: env.VERIFF_WEBHOOK_SECRET,
          });

          const payload = JSON.stringify(request.body);
          const signature = request.headers['x-signature'] as string;
          const isValid = kycProvider.verifyWebhookSignature(payload, signature);

          if (!isValid) {
            return reply.code(401).send({
              error: 'Invalid webhook signature',
            });
          }
        }

        // Process webhook if it's from Veriff
        let applicationId = body.applicationId;
        let kycResults = body.results;

        if (body.sessionId && env.VERIFF_API_KEY && env.VERIFF_API_URL && env.VERIFF_WEBHOOK_SECRET) {
          const kycProvider = new VeriffKYCProvider({
            apiKey: env.VERIFF_API_KEY,
            apiUrl: env.VERIFF_API_URL,
            webhookSecret: env.VERIFF_WEBHOOK_SECRET,
          });

          const verificationResult = await kycProvider.processWebhook(request.body);
          applicationId = verificationResult.sessionId.replace('session-', '');
          kycResults = {
            documentVerification: verificationResult.documentVerification,
            livenessCheck: verificationResult.livenessCheck,
            riskScore: verificationResult.riskScore,
          };
        }

        if (!applicationId) {
          return reply.code(400).send({
            error: 'applicationId or sessionId required',
          });
        }

        // Get application from database
        const application = await getEResidencyApplicationById(applicationId);
        if (!application) {
          return reply.code(404).send({
            error: 'Application not found',
          });
        }

        // Update application with KYC results
        await updateEResidencyApplication(applicationId, {
          kycStatus: body.status === 'approved' ? 'passed' : body.status === 'rejected' ? 'failed' : 'pending',
          kycResults: kycResults,
        });

        // Perform sanctions screening
        if (!env.SANCTIONS_API_KEY || !env.SANCTIONS_API_URL) {
          return reply.code(500).send({
            error: 'Sanctions provider not configured',
          });
        }
        const sanctionsProvider = new ComplyAdvantageSanctionsProvider({
          apiKey: env.SANCTIONS_API_KEY,
          apiUrl: env.SANCTIONS_API_URL,
        });

        const sanctionsResult = await sanctionsProvider.screenApplicant({
          applicantId: applicationId,
          givenName: application.givenName,
          familyName: application.familyName,
          dateOfBirth: application.dateOfBirth,
          nationality: application.nationality,
          address: application.address,
        });

        // Update application with sanctions results
        await updateEResidencyApplication(applicationId, {
          sanctionsStatus: sanctionsResult.sanctionsMatch ? 'flag' : sanctionsResult.pepMatch ? 'flag' : 'clear',
          pepStatus: sanctionsResult.pepMatch ? 'flag' : 'clear',
          sanctionsResults: sanctionsResult,
        });

        // Risk assessment
        const riskEngine = getRiskAssessmentEngine();
        const riskAssessment = await riskEngine.assessRisk({
          applicationId: applicationId,
          kycResults: {
            documentVerification: kycResults?.documentVerification
              ? {
                  passed: (kycResults.documentVerification as { passed: boolean }).passed,
                  documentType: (kycResults.documentVerification as { documentType: string }).documentType,
                  riskFlags: (kycResults.documentVerification as { riskFlags?: string[] }).riskFlags || [],
                }
              : undefined,
            livenessCheck: kycResults?.livenessCheck
              ? (kycResults.livenessCheck as { passed: boolean; livenessScore: number; faceMatch: boolean })
              : undefined,
            riskScore: kycResults?.riskScore,
          },
          sanctionsResults: sanctionsResult,
          applicantData: {
            nationality: application.nationality,
            address: application.address,
          },
        });

        // Update application with risk assessment
        await updateEResidencyApplication(applicationId, {
          riskScore: riskAssessment.overallRiskScore,
          riskAssessment: riskAssessment,
        });

        // Decision: Auto-approve, Auto-reject, or Manual review
        let decision: ApplicationStatus;
        if (riskAssessment.decision === 'auto_approve') {
          decision = ApplicationStatus.APPROVED;
          await updateEResidencyApplication(applicationId, {
            status: ApplicationStatus.APPROVED,
            reviewedAt: new Date().toISOString(),
          });
          // TODO: Auto-issue credential
          // await issueCredential(applicationId, config.kmsClient);
        } else if (riskAssessment.decision === 'auto_reject') {
          decision = ApplicationStatus.REJECTED;
          await updateEResidencyApplication(applicationId, {
            status: ApplicationStatus.REJECTED,
            reviewedAt: new Date().toISOString(),
            rejectionReason: riskAssessment.reasons.join('; '),
          });
        } else {
          decision = ApplicationStatus.UNDER_REVIEW;
          await updateEResidencyApplication(applicationId, {
            status: ApplicationStatus.UNDER_REVIEW,
          });
          // TODO: Add to reviewer queue
        }

        return reply.send({
          applicationId: applicationId,
          status: decision,
          riskAssessment: {
            overallRiskScore: riskAssessment.overallRiskScore,
            riskBands: riskAssessment.riskBands,
            decision: riskAssessment.decision,
            reasons: riskAssessment.reasons,
            requiresEDD: riskAssessment.requiresEDD,
            flags: riskAssessment.flags,
          },
        });
      } catch (error) {
        return reply.code(500).send({
          error: 'Failed to process KYC callback',
          message: error instanceof Error ? error.message : String(error),
        });
      }
    }
  );

  // Issue eResident VC
  server.post(
    '/issue/vc',
    {
      preHandler: [authenticateJWT, requireRole('admin', 'issuer', 'registrar')],
      schema: {
        body: {
          type: 'object',
          required: ['applicationId'],
          properties: {
            applicationId: { type: 'string' },
            residentNumber: { type: 'string' },
            publicHandle: { type: 'string' },
          },
        },
        description: 'Issue eResident VC',
        tags: ['issuance'],
      },
    },
      async (request: FastifyRequest, reply: FastifyReply) => {
      const body = request.body as {
        applicationId: string;
        residentNumber?: string;
        publicHandle?: string;
      };

      if (!issuerDid) {
        return reply.code(500).send({
          error: 'VC_ISSUER_DID or VC_ISSUER_DOMAIN must be configured',
        });
      }

      try {
        // TODO: Get application from database
        // const application = await getApplication(body.applicationId);

        // Generate resident number if not provided
        const residentNumber = body.residentNumber || `RES-${randomUUID().substring(0, 8).toUpperCase()}`;

        // Get application from database
        const application = await getEResidencyApplicationById(body.applicationId);
        if (!application) {
          return reply.code(404).send({
            error: 'Application not found',
          });
        }

        // Create credential
        const credentialId = randomUUID();
        const issuanceDate = new Date();

        const credential: eResidentCredential = {
          '@context': [
            'https://www.w3.org/2018/credentials/v1',
            'https://w3id.org/security/suites/ed25519-2020/v1',
            'https://dsb.example/context/base/v1',
            'https://dsb.example/context/eResident/v1',
          ],
          type: ['VerifiableCredential', 'eResidentCredential'],
          issuer: issuerDid,
          issuanceDate: issuanceDate.toISOString(),
          expirationDate: new Date(issuanceDate.getTime() + 365 * 24 * 60 * 60 * 1000).toISOString(), // 1 year
          credentialSubject: {
            id: `did:web:dsb.example:members:${residentNumber}`,
            legalName: `${application.givenName} ${application.familyName}`,
            publicHandle: body.publicHandle,
            assuranceLevel: LevelOfAssurance.LOA2,
            residentNumber,
            issueJurisdiction: 'DSB',
          },
          evidence: [
            {
              type: EvidenceType.DocumentVerification,
              result: EvidenceResult.pass,
              timestamp: issuanceDate.toISOString(),
            },
            {
              type: EvidenceType.LivenessCheck,
              result: EvidenceResult.pass,
              timestamp: issuanceDate.toISOString(),
            },
            {
              type: EvidenceType.SanctionsScreen,
              result: EvidenceResult.pass,
              timestamp: issuanceDate.toISOString(),
            },
          ],
          proof: {
            type: 'Ed25519Signature2020',
            created: issuanceDate.toISOString(),
            proofPurpose: 'assertionMethod',
            verificationMethod: `${issuerDid}#key-1`,
            jws: '', // Will be populated after signing
          },
        };

        // Sign credential
        const credentialJson = JSON.stringify(credential);
        const signature = await config.kmsClient.sign(Buffer.from(credentialJson));
        credential.proof.jws = signature.toString('base64');

        // Save to database
        await createVerifiableCredential({
          credential_id: credentialId,
          issuer_did: issuerDid,
          subject_did: credential.credentialSubject.id,
          credential_type: credential.type,
          credential_subject: credential.credentialSubject,
          issuance_date: issuanceDate,
          expiration_date: new Date(credential.expirationDate!),
          proof: credential.proof,
        });

        // Log audit action
        const user = (request as any).user;
        await logCredentialAction({
          credential_id: credentialId,
          issuer_did: issuerDid,
          subject_did: credential.credentialSubject.id,
          credential_type: credential.type,
          action: 'issued',
          performed_by: user?.id || user?.sub || undefined,
          metadata: {
            applicationId: body.applicationId,
            residentNumber,
          },
        });

        // Publish event
        const eventBus = getEventBus();
        await eventBus.publish('credential.issued', {
          credentialId,
          credentialType: 'eResidentCredential',
          residentNumber,
          issuedAt: issuanceDate.toISOString(),
        });

        // Send notification
        const notificationService = getNotificationService();
        await notificationService.send({
          to: application.email,
          type: 'email',
          subject: 'eResidency Credential Issued',
          message: `Your eResidency credential has been issued. Resident Number: ${residentNumber}`,
          template: 'eresidency-issued',
          templateData: {
            residentNumber,
            credentialId,
            issuedAt: issuanceDate.toISOString(),
            legalName: `${application.givenName} ${application.familyName}`,
          },
        });

        return reply.send({
          credentialId,
          residentNumber,
          credential,
        });
      } catch (error) {
        return reply.code(500).send({
          error: 'Failed to issue credential',
          message: error instanceof Error ? error.message : String(error),
        });
      }
    }
  );


  // Revoke credential
  server.post(
    '/revoke',
    {
      preHandler: [authenticateJWT, requireRole('admin', 'registrar')],
      schema: {
        body: {
          type: 'object',
          required: ['residentNumber', 'reason'],
          properties: {
            residentNumber: { type: 'string' },
            reason: { type: 'string' },
          },
        },
        description: 'Revoke eResident credential',
        tags: ['revocation'],
      },
    },
    async (request: FastifyRequest, reply: FastifyReply) => {
      const body = request.body as {
        residentNumber: string;
        reason: string;
      };
      const user = (request as any).user;

      try {
        // Find credential by resident number
        const result = await query<{
          credential_id: string;
          issuer_did: string;
          subject_did: string;
          credential_type: string[];
        }>(
          `SELECT credential_id, issuer_did, subject_did, credential_type
           FROM verifiable_credentials
           WHERE credential_subject->>'residentNumber' = $1
             AND revoked = FALSE
           ORDER BY issuance_date DESC
           LIMIT 1`,
          [body.residentNumber]
        );

        if (!result.rows[0]) {
          return reply.code(404).send({
            error: 'Credential not found or already revoked',
          });
        }

        const credential = result.rows[0]!;

        // Revoke credential
        await revokeCredential({
          credential_id: credential.credential_id,
          issuer_did: credential.issuer_did,
          revocation_reason: body.reason,
          revoked_by: user?.id || user?.sub || 'system',
        });

        // Log audit action
        await logCredentialAction({
          credential_id: credential.credential_id,
          issuer_did: credential.issuer_did,
          subject_did: credential.subject_did,
          credential_type: credential.credential_type,
          action: 'revoked',
          performed_by: user?.id || user?.sub || undefined,
          metadata: {
            residentNumber: body.residentNumber,
            reason: body.reason,
          },
        });

        // Publish event
        const eventBus = getEventBus();
        await eventBus.publish('credential.revoked', {
          credentialId: credential.credential_id,
          credentialType: 'eResidentCredential',
          residentNumber: body.residentNumber,
          reason: body.reason,
          revokedAt: new Date().toISOString(),
        });

        return reply.send({
          residentNumber: body.residentNumber,
          credentialId: credential.credential_id,
          revoked: true,
          revokedAt: new Date().toISOString(),
          reason: body.reason,
        });
      } catch (error) {
        return reply.code(500).send({
          error: 'Failed to revoke credential',
          message: error instanceof Error ? error.message : String(error),
        });
      }
    }
  );
}


