/**
 * Automated credential renewal system
 * Uses background job queue to scan for expiring credentials and issue renewals
 */

import { getExpiringCredentials, createVerifiableCredential, revokeCredential } from '@the-order/database';
import { getJobQueue } from '@the-order/jobs';
import { getEventBus, CredentialEvents } from '@the-order/events';
import { KMSClient } from '@the-order/crypto';
import { randomUUID } from 'crypto';

export interface RenewalJobData {
  credentialId: string;
  subjectDid: string;
  issuerDid: string;
  credentialType: string[];
  credentialSubject: unknown;
  expirationDate: Date;
}

/**
 * Initialize credential renewal system
 */
export async function initializeCredentialRenewal(kmsClient: KMSClient): Promise<void> {
  const jobQueue = getJobQueue();
  const eventBus = getEventBus();

  // Create renewal queue
  const renewalQueue = jobQueue.createQueue<RenewalJobData>('credential-renewal');

  // Create worker for renewal jobs
  jobQueue.createWorker<RenewalJobData>(
    'credential-renewal',
    async (job) => {
      const { credentialId, subjectDid, issuerDid, credentialType, credentialSubject, expirationDate } = job.data;

      try {
        // Issue new credential with extended expiration
        const newExpirationDate = new Date(expirationDate);
        newExpirationDate.setFullYear(newExpirationDate.getFullYear() + 1); // Extend by 1 year

        const newCredentialId = randomUUID();
        const issuanceDate = new Date();

        // Create credential data
        const credentialData = {
          id: newCredentialId,
          type: credentialType,
          issuer: issuerDid,
          subject: subjectDid,
          credentialSubject,
          issuanceDate: issuanceDate.toISOString(),
          expirationDate: newExpirationDate.toISOString(),
        };

        // Sign credential with KMS
        const credentialJson = JSON.stringify(credentialData);
        const signature = await kmsClient.sign(Buffer.from(credentialJson));

        // Create proof
        const proof = {
          type: 'KmsSignature2024',
          created: issuanceDate.toISOString(),
          proofPurpose: 'assertionMethod',
          verificationMethod: `${issuerDid}#kms-key`,
          jws: signature.toString('base64'),
        };

        // Save new credential
        await createVerifiableCredential({
          credential_id: newCredentialId,
          issuer_did: issuerDid,
          subject_did: subjectDid,
          credential_type: credentialType,
          credential_subject: credentialSubject as Record<string, unknown>,
          issuance_date: issuanceDate,
          expiration_date: newExpirationDate,
          proof,
        });

        // Revoke old credential
        await revokeCredential({
          credential_id: credentialId,
          issuer_did: issuerDid,
          revocation_reason: 'Renewed - replaced by new credential',
        });

        // Publish renewal event
        await eventBus.publish(CredentialEvents.RENEWED, {
          oldCredentialId: credentialId,
          newCredentialId,
          subjectDid,
          renewedAt: issuanceDate.toISOString(),
        });

        return { success: true, newCredentialId };
      } catch (error) {
        console.error(`Failed to renew credential ${credentialId}:`, error);
        throw error;
      }
    },
    { concurrency: 5 }
  );

  // Create worker for expiration scanner
  jobQueue.createWorker<{ daysAhead: number }>(
    'credential-expiration-scanner',
    async (job) => {
      const { daysAhead } = job.data;

      // Get expiring credentials
      const expiringCredentials = await getExpiringCredentials(daysAhead, 1000);

      // Publish expiring event for each
      for (const cred of expiringCredentials) {
        await eventBus.publish(CredentialEvents.EXPIRING, {
          credentialId: cred.credential_id,
          subjectDid: cred.subject_did,
          expirationDate: cred.expiration_date!.toISOString(),
          daysUntilExpiration: Math.ceil(
            (cred.expiration_date!.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
          ),
        });

        // For credentials expiring in 30 days or less, queue for renewal
        const daysUntilExpiration = Math.ceil(
          (cred.expiration_date!.getTime() - Date.now()) / (1000 * 60 * 60 * 24)
        );

        if (daysUntilExpiration <= 30) {
          // Queue renewal job with credential data
          await renewalQueue.add('default', {
            credentialId: cred.credential_id,
            subjectDid: cred.subject_did,
            issuerDid: cred.issuer_did,
            credentialType: cred.credential_type,
            credentialSubject: cred.credential_subject as Record<string, unknown>,
            expirationDate: cred.expiration_date!,
          } as RenewalJobData);
        }
      }

      return { scanned: expiringCredentials.length };
    }
  );

  // Schedule recurring job to scan for expiring credentials daily at 2 AM
  await jobQueue.addRecurringJob<{ daysAhead: number }>(
    'credential-expiration-scanner',
    { daysAhead: 90 }, // Check credentials expiring in next 90 days
    '0 2 * * *' // Run daily at 2 AM
  );
}

/**
 * Manually trigger renewal for a specific credential
 */
export async function triggerRenewal(
  credentialId: string,
  _kmsClient: KMSClient
): Promise<void> {
  const jobQueue = getJobQueue();
  const renewalQueue = jobQueue.createQueue<RenewalJobData>('credential-renewal');

  // Get credential details
  const { getVerifiableCredentialById } = await import('@the-order/database');
  const credential = await getVerifiableCredentialById(credentialId);

  if (!credential) {
    throw new Error(`Credential ${credentialId} not found`);
  }

  if (!credential.expiration_date) {
    throw new Error(`Credential ${credentialId} has no expiration date`);
  }

  // Queue renewal job
  await renewalQueue.add('default', {
    credentialId: credential.credential_id,
    subjectDid: credential.subject_did,
    issuerDid: credential.issuer_did,
    credentialType: credential.credential_type,
    credentialSubject: credential.credential_subject as Record<string, unknown>,
    expirationDate: credential.expiration_date,
  } as RenewalJobData);
}

