import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

/**
 * Secrets Management Service
 *
 * Provides a unified interface for accessing secrets from various sources:
 * - Development: .env files
 * - Staging/Production: AWS Secrets Manager, HashiCorp Vault, or environment variables
 *
 * Usage:
 * ```typescript
 * const secret = await secretsService.getSecret('JWT_SECRET');
 * const dbPassword = await secretsService.getSecret('DATABASE_PASSWORD');
 * ```
 */
@Injectable()
export class SecretsService {
  private readonly logger = new Logger(SecretsService.name);
  private secretsCache: Map<string, { value: string; expiresAt: number }> = new Map();
  private readonly cacheTTL = 300000; // 5 minutes

  constructor(private configService: ConfigService) {}

  /**
   * Get a secret value from the configured secrets provider
   *
   * @param secretName - The name of the secret to retrieve
   * @param defaultValue - Optional default value if secret is not found
   * @returns The secret value
   */
  async getSecret(secretName: string, defaultValue?: string): Promise<string | undefined> {
    const environment = this.configService.get<string>('NODE_ENV', 'development');

    // Check cache first
    const cached = this.secretsCache.get(secretName);
    if (cached && cached.expiresAt > Date.now()) {
      return cached.value;
    }

    try {
      let secretValue: string | undefined;

      // In development, use .env files directly
      if (environment === 'development') {
        secretValue = this.configService.get<string>(secretName, defaultValue);
      } else {
        // In staging/production, use secrets provider
        secretValue = await this.getSecretFromProvider(secretName, defaultValue);
      }

      // Cache the secret
      if (secretValue) {
        this.secretsCache.set(secretName, {
          value: secretValue,
          expiresAt: Date.now() + this.cacheTTL,
        });
      }

      return secretValue;
    } catch (error) {
      this.logger.error(`Failed to retrieve secret ${secretName}:`, error);
      return defaultValue;
    }
  }

  /**
   * Get secret from the configured provider (AWS Secrets Manager, Vault, etc.)
   */
  private async getSecretFromProvider(secretName: string, defaultValue?: string): Promise<string | undefined> {
    const secretsProvider = this.configService.get<string>('SECRETS_PROVIDER', 'env');

    switch (secretsProvider) {
      case 'aws':
        return this.getFromAWSSecretsManager(secretName, defaultValue);
      case 'vault':
        return this.getFromVault(secretName, defaultValue);
      default:
        // Fallback to environment variables
        return this.configService.get<string>(secretName, defaultValue);
    }
  }

  /**
   * Get secret from AWS Secrets Manager
   *
   * To use AWS Secrets Manager:
   * 1. Install: npm install @aws-sdk/client-secrets-manager
   * 2. Set environment variable: SECRETS_PROVIDER=aws
   * 3. Configure AWS credentials (IAM role, access keys, or environment variables)
   */
  private async getFromAWSSecretsManager(secretName: string, defaultValue?: string): Promise<string | undefined> {
    try {
      // TODO: Implement AWS Secrets Manager integration
      // Uncomment when @aws-sdk/client-secrets-manager is installed

      /*
      const { SecretsManagerClient, GetSecretValueCommand } = await import('@aws-sdk/client-secrets-manager');

      const client = new SecretsManagerClient({
        region: this.configService.get<string>('AWS_REGION', 'us-east-1'),
      });

      const response = await client.send(
        new GetSecretValueCommand({
          SecretId: secretName,
          VersionStage: 'AWSCURRENT',
        })
      );

      return response.SecretString || defaultValue;
      */

      this.logger.warn(`AWS Secrets Manager not configured. Using environment variable for ${secretName}`);
      return this.configService.get<string>(secretName, defaultValue);
    } catch (error) {
      this.logger.error(`Failed to get secret from AWS Secrets Manager: ${secretName}`, error);
      return defaultValue;
    }
  }

  /**
   * Get secret from HashiCorp Vault
   *
   * To use HashiCorp Vault:
   * 1. Install: npm install node-vault
   * 2. Set environment variable: SECRETS_PROVIDER=vault
   * 3. Configure: VAULT_ADDR, VAULT_TOKEN
   */
  private async getFromVault(secretName: string, defaultValue?: string): Promise<string | undefined> {
    try {
      // TODO: Implement HashiCorp Vault integration
      // Uncomment when node-vault is installed

      /*
      const vault = require('node-vault')({
        apiVersion: 'v1',
        endpoint: this.configService.get<string>('VAULT_ADDR', 'http://localhost:8200'),
        token: this.configService.get<string>('VAULT_TOKEN'),
      });

      const result = await vault.read(`secret/data/${secretName}`);
      return result.data.data.value || defaultValue;
      */

      this.logger.warn(`HashiCorp Vault not configured. Using environment variable for ${secretName}`);
      return this.configService.get<string>(secretName, defaultValue);
    } catch (error) {
      this.logger.error(`Failed to get secret from Vault: ${secretName}`, error);
      return defaultValue;
    }
  }

  /**
   * Get multiple secrets at once
   */
  async getSecrets(secretNames: string[]): Promise<Record<string, string | undefined>> {
    const secrets: Record<string, string | undefined> = {};

    await Promise.all(
      secretNames.map(async (name) => {
        secrets[name] = await this.getSecret(name);
      })
    );

    return secrets;
  }

  /**
   * Clear the secrets cache
   */
  clearCache(): void {
    this.secretsCache.clear();
    this.logger.log('Secrets cache cleared');
  }

  /**
   * Refresh a specific secret in the cache
   */
  async refreshSecret(secretName: string): Promise<string | undefined> {
    this.secretsCache.delete(secretName);
    return this.getSecret(secretName);
  }

  /**
   * Validate that all required secrets are present
   */
  async validateRequiredSecrets(requiredSecrets: string[]): Promise<{ valid: boolean; missing: string[] }> {
    const missing: string[] = [];

    for (const secretName of requiredSecrets) {
      const value = await this.getSecret(secretName);
      if (!value) {
        missing.push(secretName);
      }
    }

    const valid = missing.length === 0;

    if (!valid) {
      this.logger.error(`Missing required secrets: ${missing.join(', ')}`);
    }

    return { valid, missing };
  }
}
