/**
 * Stripe Keys Service
 *
 * Manages organization-specific Stripe API keys with encryption.
 * Supports both test and live mode keys with automatic detection.
 *
 * Security:
 * - Secret keys and webhook secrets are encrypted at rest using AES-256-GCM
 * - Publishable keys are stored in plaintext (safe to expose client-side)
 * - Keys are masked for UI display
 * - Validation against Stripe API before saving
 */

import { encrypt, decrypt } from '@/lib/encryption';
import { logger } from '@/lib/logger';
import { organizationsAdapter } from '@/lib/adapters/organizations';
import Stripe from 'stripe';

export interface StripeKeysConfig {
  secretKey: string;
  publishableKey: string;
  webhookSecret?: string;
}

export interface MaskedStripeKeysConfig {
  secretKeyMasked: string;
  publishableKey: string;
  webhookSecretMasked?: string;
  mode: 'test' | 'live';
  configuredAt: Date | null;
  configuredBy: string | null;
}

class StripeKeysService {
  /**
   * Get masked keys for UI display
   * Returns null if organization has no keys configured
   */
  async getMaskedKeys(
    organizationId: string
  ): Promise<MaskedStripeKeysConfig | null> {
    try {
      const org = await organizationsAdapter.findUnique({
        where: { id: organizationId },
        select: {
          stripeSecretKeyEncrypted: true,
          stripePublishableKey: true,
          stripeWebhookSecretEncrypted: true,
          stripeKeysMode: true,
          stripeKeysConfiguredAt: true,
          stripeKeysConfiguredBy: true,
        },
      }) as {
        stripeSecretKeyEncrypted: string | null;
        stripePublishableKey: string | null;
        stripeWebhookSecretEncrypted: string | null;
        stripeKeysMode: string | null;
        stripeKeysConfiguredAt: Date | null;
        stripeKeysConfiguredBy: string | null;
      } | null;

      if (!org || !org.stripeSecretKeyEncrypted || !org.stripePublishableKey) {
        return null;
      }

      // Use stored mode or detect from key
      let mode: 'test' | 'live' = 'test';
      if (org.stripeKeysMode) {
        mode = org.stripeKeysMode as 'test' | 'live';
      } else {
        // Decrypt secret key just to detect mode (not returned)
        try {
          const decryptedSecretKey = decrypt(org.stripeSecretKeyEncrypted);
          mode = this.detectMode(decryptedSecretKey);
        } catch (error) {
          logger.error('Failed to decrypt secret key for mode detection', {
            organizationId,
            error,
          });
        }
      }

      return {
        secretKeyMasked: this.maskKey(org.stripeSecretKeyEncrypted),
        publishableKey: org.stripePublishableKey,
        webhookSecretMasked: org.stripeWebhookSecretEncrypted
          ? this.maskKey(org.stripeWebhookSecretEncrypted)
          : undefined,
        mode,
        configuredAt: org.stripeKeysConfiguredAt,
        configuredBy: org.stripeKeysConfiguredBy,
      };
    } catch (error) {
      logger.error('Failed to get masked Stripe keys', {
        organizationId,
        error,
      });
      return null;
    }
  }

  /**
   * Save Stripe keys for an organization
   * Keys are encrypted before storage
   */
  async saveKeys(
    organizationId: string,
    keys: StripeKeysConfig,
    staffId: string
  ): Promise<boolean> {
    try {
      // Validate keys with Stripe API first
      const validation = await this.validateKeys(keys.secretKey, keys.publishableKey);
      if (!validation.valid) {
        logger.warn('Invalid Stripe keys provided', {
          organizationId,
          staffId,
          error: validation.error
        });
        return false;
      }

      // Encrypt sensitive keys
      const encryptedSecretKey = encrypt(keys.secretKey);
      const encryptedWebhookSecret = keys.webhookSecret
        ? encrypt(keys.webhookSecret)
        : null;

      const mode = validation.mode || this.detectMode(keys.secretKey);

      // Save to database
      await organizationsAdapter.update({
        where: { id: organizationId },
        data: {
          stripeSecretKeyEncrypted: encryptedSecretKey,
          stripePublishableKey: keys.publishableKey, // Stored in plaintext (safe)
          stripeWebhookSecretEncrypted: encryptedWebhookSecret,
          stripeKeysMode: mode,
          stripeKeysConfiguredAt: new Date(),
          stripeKeysConfiguredBy: staffId,
        },
      });

      logger.info('Stripe keys saved successfully', {
        organizationId,
        staffId,
        mode,
        accountName: validation.accountName,
      });

      return true;
    } catch (error) {
      logger.error('Failed to save Stripe keys', {
        organizationId,
        staffId,
        error,
      });
      return false;
    }
  }

  /**
   * Get decrypted secret key for API calls
   * Returns null if not configured or decryption fails
   */
  async getSecretKey(organizationId: string): Promise<string | null> {
    try {
      const org = await organizationsAdapter.findUnique({
        where: { id: organizationId },
        select: { stripeSecretKeyEncrypted: true },
      }) as { stripeSecretKeyEncrypted: string | null } | null;

      if (!org || !org.stripeSecretKeyEncrypted) {
        return null;
      }

      return decrypt(org.stripeSecretKeyEncrypted);
    } catch (error) {
      logger.error('Failed to get Stripe secret key', {
        organizationId,
        error,
      });
      return null;
    }
  }

  /**
   * Get decrypted webhook secret
   * Returns null if not configured or decryption fails
   */
  async getWebhookSecret(organizationId: string): Promise<string | null> {
    try {
      const org = await organizationsAdapter.findUnique({
        where: { id: organizationId },
        select: { stripeWebhookSecretEncrypted: true },
      }) as { stripeWebhookSecretEncrypted: string | null } | null;

      if (!org || !org.stripeWebhookSecretEncrypted) {
        return null;
      }

      return decrypt(org.stripeWebhookSecretEncrypted);
    } catch (error) {
      logger.error('Failed to get Stripe webhook secret', {
        organizationId,
        error,
      });
      return null;
    }
  }

  /**
   * Delete Stripe keys for an organization
   */
  async deleteKeys(
    organizationId: string,
    staffId: string
  ): Promise<boolean> {
    try {
      await organizationsAdapter.update({
        where: { id: organizationId },
        data: {
          stripeSecretKeyEncrypted: null,
          stripePublishableKey: null,
          stripeWebhookSecretEncrypted: null,
          stripeKeysMode: null,
          stripeKeysConfiguredAt: null,
          stripeKeysConfiguredBy: null,
        },
      });

      logger.info('Stripe keys deleted', { organizationId, staffId });
      return true;
    } catch (error) {
      logger.error('Failed to delete Stripe keys', {
        organizationId,
        staffId,
        error,
      });
      return false;
    }
  }

  /**
   * Check if organization has configured Stripe keys
   */
  async hasKeys(organizationId: string): Promise<boolean> {
    try {
      const org = await organizationsAdapter.findUnique({
        where: { id: organizationId },
        select: { stripeSecretKeyEncrypted: true, stripePublishableKey: true },
      }) as { stripeSecretKeyEncrypted: string | null; stripePublishableKey: string | null } | null;

      return !!(org?.stripeSecretKeyEncrypted && org?.stripePublishableKey);
    } catch (error) {
      logger.error('Failed to check if organization has Stripe keys', {
        organizationId,
        error,
      });
      return false;
    }
  }

  /**
   * Validate Stripe keys by calling Stripe API
   * Returns true if keys are valid
   */
  async validateKeys(secretKey: string, publishableKey?: string): Promise<{
    valid: boolean;
    mode?: 'test' | 'live';
    accountName?: string;
    error?: string;
  }> {
    try {
      // Check mode match if both keys provided
      if (publishableKey) {
        const secretMode = this.detectMode(secretKey);
        const publishableMode = publishableKey.startsWith('pk_test_') ? 'test' : 'live';

        if (secretMode !== publishableMode) {
          return {
            valid: false,
            error: 'Secret key and publishable key must be in the same mode (both test or both live)',
          };
        }
      }

      const stripe = new Stripe(secretKey, {
        apiVersion: '2025-10-29.clover',
      });

      // Attempt to retrieve account info
      const account = await stripe.accounts.retrieve();

      return {
        valid: true,
        mode: this.detectMode(secretKey),
        accountName: account.business_profile?.name || account.email || 'Unknown',
      };
    } catch (error) {
      logger.warn('Stripe key validation failed', {
        error:
          error instanceof Error ? error.message : 'Unknown validation error',
      });
      return {
        valid: false,
        error: error instanceof Error ? error.message : 'Invalid Stripe API key',
      };
    }
  }

  /**
   * Mask a key for display in UI
   * Format: sk_test_•••••••xxxx or whsec_•••••••xxxx
   */
  private maskKey(key: string): string {
    if (!key) {
      return '••••••••';
    }

    // For encrypted values, just show generic mask
    if (key.includes(':')) {
      return '••••••••••••••••';
    }

    // Show first 7 and last 4 characters
    if (key.length <= 11) {
      return '••••••••';
    }

    const prefix = key.substring(0, 7); // e.g., "sk_test" or "whsec_"
    const suffix = key.substring(key.length - 4);

    return `${prefix}••••••••${suffix}`;
  }

  /**
   * Detect mode (test/live) from secret key prefix
   */
  private detectMode(secretKey: string): 'test' | 'live' {
    if (secretKey.startsWith('sk_test_')) {
      return 'test';
    }
    if (secretKey.startsWith('sk_live_')) {
      return 'live';
    }

    // Default to test for safety
    logger.warn('Could not detect Stripe key mode, defaulting to test', {
      prefix: secretKey.substring(0, 8),
    });
    return 'test';
  }
}

export const stripeKeysService = new StripeKeysService();
