/**
 * Scheduled credential issuance
 * Cron-based jobs for renewal, event-driven issuance, batch operations, expiration detection
 */

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

export interface ScheduledIssuanceConfig {
  kmsClient: KMSClient;
  enableExpirationDetection?: boolean;
  enableBatchRenewal?: boolean;
  enableScheduledIssuance?: boolean;
}

/**
 * Initialize scheduled credential issuance
 */
export async function initializeScheduledIssuance(
  config: ScheduledIssuanceConfig
): Promise<void> {
  const jobQueue = getJobQueue();
  const eventBus = getEventBus();
  const env = getEnv();
  const issuerDid = env.VC_ISSUER_DID || (env.VC_ISSUER_DOMAIN ? `did:web:${env.VC_ISSUER_DOMAIN}` : undefined);

  if (!issuerDid) {
    throw new Error('VC_ISSUER_DID or VC_ISSUER_DOMAIN must be configured');
  }

  // Create scheduled issuance queue
  const scheduledQueue = jobQueue.createQueue<{
    credentialType: string[];
    subjectDid: string;
    credentialSubject: Record<string, unknown>;
    scheduledDate: Date;
  }>('scheduled-issuance');

  // Create worker for scheduled issuance
  jobQueue.createWorker(
    'scheduled-issuance',
    async (job) => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const jobData = job.data as any;
      const { credentialType, subjectDid, credentialSubject, scheduledDate } = jobData;
      const scheduledDateObj = scheduledDate instanceof Date ? scheduledDate : new Date(scheduledDate as string);

      // Check if it's time to issue
      if (new Date() < scheduledDateObj) {
        // Reschedule for later
        await scheduledQueue.add('default' as any, jobData, {
          delay: scheduledDateObj.getTime() - Date.now(),
        });
        return { rescheduled: true };
      }

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

      const credentialData = {
        id: credentialId,
        type: credentialType,
        issuer: issuerDid,
        subject: subjectDid,
        credentialSubject,
        issuanceDate: issuanceDate.toISOString(),
      };

      const credentialJson = JSON.stringify(credentialData);
      const signature = await config.kmsClient.sign(Buffer.from(credentialJson));

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

      await createVerifiableCredential({
        credential_id: credentialId,
        issuer_did: issuerDid,
        subject_did: subjectDid as string,
        credential_type: credentialType as string[],
        credential_subject: credentialSubject as Record<string, unknown>,
        issuance_date: issuanceDate,
        expiration_date: undefined,
        proof,
      });

      await eventBus.publish(CredentialEvents.ISSUED, {
        subjectDid,
        credentialType,
        credentialId,
        issuedAt: issuanceDate.toISOString(),
      });

      return { credentialId, issued: true };
    }
  );

  // Expiration detection job (runs daily at 1 AM)
  if (config.enableExpirationDetection) {
    const expirationQueue = jobQueue.createQueue<{ daysAhead: number }>('expiration-detection');
    await expirationQueue.add('default' as any, { daysAhead: 90 }, {
      repeat: {
        pattern: '0 1 * * *', // Daily at 1 AM
      },
    });

    jobQueue.createWorker('expiration-detection', async (job) => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const { daysAhead } = job.data as any;
      const expiring = await getExpiringCredentials(daysAhead as number, 1000);

      for (const cred of expiring) {
        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)
          ),
        });
      }

      return { detected: expiring.length };
    });
  }

  // Batch renewal job (runs weekly on Sunday at 2 AM)
  if (config.enableBatchRenewal) {
    const batchRenewalQueue = jobQueue.createQueue<{ daysAhead: number }>('batch-renewal');
    await batchRenewalQueue.add('default' as any, { daysAhead: 30 }, {
      repeat: {
        pattern: '0 2 * * 0', // Weekly on Sunday at 2 AM
      },
    });

    jobQueue.createWorker('batch-renewal', async (job) => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const { daysAhead } = job.data as any;
      const expiring = await getExpiringCredentials(daysAhead as number, 100);

      let renewed = 0;
      for (const cred of expiring) {
        try {
          // Issue new credential
          const newCredentialId = randomUUID();
          const issuanceDate = new Date();
          const newExpirationDate = new Date(cred.expiration_date);
          newExpirationDate.setFullYear(newExpirationDate.getFullYear() + 1);

          const credentialData = {
            id: newCredentialId,
            type: cred.credential_type,
            issuer: cred.issuer_did,
            subject: cred.subject_did,
            credentialSubject: cred.credential_subject as Record<string, unknown>,
            issuanceDate: issuanceDate.toISOString(),
            expirationDate: newExpirationDate.toISOString(),
          };

          const credentialJson = JSON.stringify(credentialData);
          const signature = await config.kmsClient.sign(Buffer.from(credentialJson));

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

          await createVerifiableCredential({
            credential_id: newCredentialId,
            issuer_did: cred.issuer_did,
            subject_did: cred.subject_did,
            credential_type: cred.credential_type,
            credential_subject: cred.credential_subject as Record<string, unknown>,
            issuance_date: issuanceDate,
            expiration_date: newExpirationDate,
            proof,
          });

          // Revoke old credential
          await revokeCredential({
            credential_id: cred.credential_id,
            issuer_did: cred.issuer_did,
            revocation_reason: 'Renewed via batch renewal',
          });

          await eventBus.publish(CredentialEvents.RENEWED, {
            oldCredentialId: cred.credential_id,
            newCredentialId,
            subjectDid: cred.subject_did,
            renewedAt: issuanceDate.toISOString(),
          });

          renewed++;
        } catch (error) {
          console.error(`Failed to renew credential ${cred.credential_id}:`, error);
        }
      }

      return { renewed, total: expiring.length };
    });
  }
}

/**
 * Schedule credential issuance for a future date
 */
export async function scheduleCredentialIssuance(params: {
  credentialType: string[];
  subjectDid: string;
  credentialSubject: Record<string, unknown>;
  scheduledDate: Date;
}): Promise<string> {
  const jobQueue = getJobQueue();
  const scheduledQueue = jobQueue.createQueue<typeof params>('scheduled-issuance');

  const delay = params.scheduledDate.getTime() - Date.now();
  if (delay <= 0) {
    throw new Error('Scheduled date must be in the future');
  }

  const job = await scheduledQueue.add('default' as any, params, {
    delay,
  });

  return job.id!;
}

