import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
import fs from 'fs';
import path from 'path';
import logger from '../common/utils/logger.js';

/** Never log raw passwords — show length only */
function maskSecretForLog(secretName: string, value: string | null): string {
  if (value === null) return '(not found)';
  if (!value) return '(empty)';
  if (/PASSWORD|SECRET|TOKEN|PRIVATE_KEY/i.test(secretName)) {
    return `*** (${value.length} characters)`;
  }
  return value;
}

type CredentialsMeta = {
  projectId: string | null;
  clientEmail: string | null;
  keyPath: string;
};

/** SMTP-related secrets loaded from Google Secret Manager */
export const SMTP_SECRET_NAMES = [
  'SMTP_HOST',
  'SMTP_PORT',
  'SMTP_USER',
  'SMTP_PASSWORD',
  'SMTP_SECURE',
  'EMAIL_FROM',
] as const;

export type SmtpSecretName = (typeof SMTP_SECRET_NAMES)[number];

/**
 * Default mapping: GSM secret name → process.env key used by this app (.env.example uses EMAIL_*).
 * Override via GCP_SECRET_MAP_FILE (JSON object).
 */
export const DEFAULT_SMTP_SECRET_ENV_MAP: Record<string, string> = {
  SMTP_HOST: 'EMAIL_HOST',
  SMTP_PORT: 'EMAIL_PORT',
  SMTP_USER: 'EMAIL_USER',
  SMTP_PASSWORD: 'EMAIL_PASSWORD',
  SMTP_SECURE: 'EMAIL_SECURE',
  EMAIL_FROM: 'EMAIL_FROM',
};

/**
 * Google Secret Manager — loads secrets into process.env (SMTP-focused helpers included).
 *
 * Env:
 * - USE_GOOGLE_SECRET_MANAGER=true
 * - GCP_KEY_FILE (service account JSON — project_id is read from this file)
 * - GCP_PROJECT_ID (optional override; otherwise uses project_id from credentials JSON)
 * - GCP_SECRET_PREFIX (optional)
 * - GCP_SECRET_MAP_FILE (optional JSON: secretName → envVarName)
 */
class GoogleSecretManagerService {
  private client: SecretManagerServiceClient | null = null;
  private projectId = '';
  private secretPrefix: string;
  private secretMap: Record<string, string> = { ...DEFAULT_SMTP_SECRET_ENV_MAP };
  private isInitialized = false;
  private isLoaded = false;

  constructor() {
    this.secretPrefix = process.env.GCP_SECRET_PREFIX || '';
    this.loadSecretMapFile();
  }

  private loadSecretMapFile(): void {
    const mapFile = process.env.GCP_SECRET_MAP_FILE;
    if (!mapFile) return;

    try {
      const mapFilePath = path.isAbsolute(mapFile) ? mapFile : path.resolve(process.cwd(), mapFile);
      if (!fs.existsSync(mapFilePath)) {
        logger.warn(`[Secret Manager] Secret mapping file not found: ${mapFilePath}`);
        return;
      }
      const mapContent = fs.readFileSync(mapFilePath, 'utf8');
      const customMap = JSON.parse(mapContent) as Record<string, string>;
      this.secretMap = { ...this.secretMap, ...customMap };
      logger.info(`[Secret Manager] Loaded secret mapping from ${mapFilePath}`);
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      logger.warn(`[Secret Manager] Failed to load secret mapping file: ${message}`);
    }
  }

  isEnabled(): boolean {
    return process.env.USE_GOOGLE_SECRET_MANAGER === 'true';
  }

  private resolveCredentialsPath(envPath: string): string | null {
    if (!envPath.trim()) return null;

    const resolved = path.isAbsolute(envPath)
      ? envPath
      : path.resolve(process.cwd(), envPath);

    return fs.existsSync(resolved) ? resolved : null;
  }

  private resolveKeyFilePath(): string | null {
    const keyFilePath = process.env.GCP_KEY_FILE || '';
    if (!keyFilePath) return null;

    const resolved = this.resolveCredentialsPath(keyFilePath);
    if (resolved) return resolved;

    logger.warn(
      `[Secret Manager] Key file not found at: ${path.isAbsolute(keyFilePath) ? keyFilePath : path.resolve(process.cwd(), keyFilePath)}`
    );
    return null;
  }

  private readCredentialsMeta(filePath: string): CredentialsMeta {
    try {
      const content = fs.readFileSync(filePath, 'utf8');
      const parsed = JSON.parse(content) as { project_id?: string; client_email?: string };
      return {
        projectId: parsed.project_id?.trim() || null,
        clientEmail: parsed.client_email?.trim() || null,
        keyPath: filePath,
      };
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      logger.warn(`[Secret Manager] Could not parse credentials file ${filePath}: ${message}`);
      return { projectId: null, clientEmail: null, keyPath: filePath };
    }
  }

  private readProjectIdFromCredentialsFile(filePath: string): string | null {
    return this.readCredentialsMeta(filePath).projectId;
  }

  private logStartupContext(secretNames: readonly string[]): void {
    const keyPath = this.resolveKeyFilePath();
    const enabled = this.isEnabled();

    logger.info('[Secret Manager] ─── Google Secret Manager ───');
    logger.info(`[Secret Manager] Enabled: ${enabled}`);
    logger.info(`[Secret Manager] USE_GOOGLE_SECRET_MANAGER=${process.env.USE_GOOGLE_SECRET_MANAGER ?? '(unset)'}`);

    if (!enabled) {
      logger.info('[Secret Manager] Skipping — set USE_GOOGLE_SECRET_MANAGER=true to load secrets');
      return;
    }

    if (keyPath) {
      const meta = this.readCredentialsMeta(keyPath);
      logger.info(`[Secret Manager] Credentials file: ${keyPath}`);
      if (meta.clientEmail) {
        logger.info(`[Secret Manager] Service account: ${meta.clientEmail}`);
      }
      if (meta.projectId) {
        logger.info(`[Secret Manager] project_id (from file): ${meta.projectId}`);
      }
    } else {
      logger.warn(`[Secret Manager] GCP_KEY_FILE=${process.env.GCP_KEY_FILE ?? '(unset)'} — file not found`);
    }

    if (process.env.GCP_PROJECT_ID?.trim()) {
      logger.info(`[Secret Manager] GCP_PROJECT_ID override: ${process.env.GCP_PROJECT_ID.trim()}`);
    }

    if (this.secretPrefix) {
      logger.info(`[Secret Manager] Secret prefix: "${this.secretPrefix}-"`);
    }

    logger.info(`[Secret Manager] Secrets to load (${secretNames.length}):`);
    for (const secretName of secretNames) {
      const gsmName = this.secretPrefix ? `${this.secretPrefix}-${secretName}` : secretName;
      const envVar = this.getEnvVarName(secretName);
      logger.info(`[Secret Manager]   • GSM "${gsmName}" → process.env.${envVar}`);
    }
  }

  /**
   * Resolve GCP project ID: env override → project_id field in service account JSON.
   */
  private resolveProjectId(): string {
    const envOverride = process.env.GCP_PROJECT_ID?.trim();
    if (envOverride) {
      logger.debug(`[Secret Manager] Using GCP_PROJECT_ID from environment: ${envOverride}`);
      return envOverride;
    }

    const keyPath =
      this.resolveKeyFilePath() ||
      this.resolveCredentialsPath(process.env.GOOGLE_APPLICATION_CREDENTIALS || '');

    if (keyPath) {
      const fromFile = this.readProjectIdFromCredentialsFile(keyPath);
      if (fromFile) {
        logger.info(`[Secret Manager] Project ID from credentials file: ${fromFile}`);
        return fromFile;
      }
      logger.warn(`[Secret Manager] No project_id in credentials file: ${keyPath}`);
    } else {
      logger.warn('[Secret Manager] No credentials file found (set GCP_KEY_FILE)');
    }

    return '';
  }

  private ensureProjectId(): boolean {
    if (!this.projectId) {
      this.projectId = this.resolveProjectId();
    }
    return Boolean(this.projectId);
  }

  private async initializeClient(): Promise<void> {
    if (this.client) return;

    if (!this.ensureProjectId()) {
      throw new Error(
        'GCP project ID missing: set GCP_PROJECT_ID or use a credentials JSON with project_id'
      );
    }

    const resolvedKeyPath = this.resolveKeyFilePath();

    if (resolvedKeyPath) {
      logger.info(`[Secret Manager] Authenticating with key file: ${resolvedKeyPath}`);
    } else if (process.env.GCP_KEY_FILE) {
      logger.warn('[Secret Manager] Key file missing — will try Application Default Credentials');
    } else {
      logger.warn('[Secret Manager] GCP_KEY_FILE not set — will try Application Default Credentials');
    }

    try {
      this.client = new SecretManagerServiceClient({
        projectId: this.projectId,
        ...(resolvedKeyPath ? { keyFilename: resolvedKeyPath } : {}),
      });
      logger.info(
        `[Secret Manager] Client ready (project: ${this.projectId}, package: @google-cloud/secret-manager)`
      );
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error(`[Secret Manager] Failed to initialize client: ${message}`);
      if (message.includes('Could not load the default credentials')) {
        logger.error('[Secret Manager] Fix: set GCP_KEY_FILE to a valid service account JSON path');
      }
      throw error;
    }
  }

  private async fetchSecretPayload(secretName: string): Promise<string | null> {
    if (!this.client || !this.projectId) return null;

    const fullSecretName = this.secretPrefix ? `${this.secretPrefix}-${secretName}` : secretName;
    const name = `projects/${this.projectId}/secrets/${fullSecretName}/versions/latest`;
    const envVar = this.getEnvVarName(secretName);

    logger.info(`[Secret Manager] Fetching "${fullSecretName}" (${name})`);

    try {
      const [version] = await this.client.accessSecretVersion({ name });
      if (!version.payload?.data) {
        logger.warn(`[Secret Manager]   ✗ "${fullSecretName}" — empty payload`);
        return null;
      }
      const raw = version.payload.data.toString().trim();
      logger.info(
        `[Secret Manager]   ✓ "${fullSecretName}" → ${envVar} = ${maskSecretForLog(secretName, raw)}`
      );
      return raw;
    } catch (error: unknown) {
      const err = error as { code?: number | string; message?: string };
      const isEmailSecret = /^(EMAIL_|SMTP_)/i.test(secretName);

      if (err.code === 5 || err.code === 'NOT_FOUND' || err.message?.includes('not found')) {
        const msg = `[Secret Manager] Secret not found: ${fullSecretName}`;
        if (isEmailSecret) logger.info(msg);
        else logger.debug(msg);
        return null;
      }

      if (err.code === 7 || err.code === 'PERMISSION_DENIED' || err.message?.includes('Permission denied')) {
        logger.warn(`[Secret Manager] Permission denied for secret: ${fullSecretName}`);
        return null;
      }

      logger.warn(
        `[Secret Manager] Failed to fetch '${fullSecretName}': ${err.message || 'unknown error'}`
      );
      return null;
    }
  }

  private getEnvVarName(secretName: string): string {
    return this.secretMap[secretName] || secretName.toUpperCase().replace(/-/g, '_');
  }

  /**
   * Load secrets and merge into process.env (does not override existing non-empty env vars).
   */
  async loadSecrets(secretNames: readonly string[]): Promise<Record<string, string>> {
    const loaded: Record<string, string> = {};
    const appliedToEnv: string[] = [];
    const skippedLocal: string[] = [];
    const notFound: string[] = [];

    this.logStartupContext(secretNames);

    if (!this.isEnabled()) {
      return loaded;
    }

    if (!this.ensureProjectId()) {
      logger.warn(
        '[Secret Manager] Project ID not resolved — set GCP_KEY_FILE with project_id in JSON, or GCP_PROJECT_ID'
      );
      return loaded;
    }

    try {
      await this.initializeClient();
      if (!this.client) return loaded;

      logger.info(`[Secret Manager] Starting fetch (project: ${this.projectId})...`);

      for (const secretName of secretNames) {
        const value = await this.fetchSecretPayload(secretName);
        const envVar = this.getEnvVarName(secretName);

        if (value === null) {
          notFound.push(secretName);
          continue;
        }

        loaded[envVar] = value;

        const alreadySet = process.env[envVar] !== undefined && process.env[envVar] !== '';
        if (alreadySet) {
          skippedLocal.push(`${secretName}→${envVar} (local .env wins)`);
          logger.info(
            `[Secret Manager]   ⊘ Applied to memory only — ${envVar} already set in .env (${maskSecretForLog(secretName, process.env[envVar]!)})`
          );
          continue;
        }

        process.env[envVar] = value;
        appliedToEnv.push(`${secretName}→${envVar}`);
        logger.info(`[Secret Manager]   → Set process.env.${envVar}`);
      }

      this.isLoaded = true;
      this.isInitialized = true;

      logger.info('[Secret Manager] ─── Load summary ───');
      logger.info(
        `[Secret Manager] Fetched from GSM: ${Object.keys(loaded).length}/${secretNames.length}`
      );
      if (appliedToEnv.length > 0) {
        logger.info(`[Secret Manager] Applied to process.env: ${appliedToEnv.join(', ')}`);
      }
      if (skippedLocal.length > 0) {
        logger.info(`[Secret Manager] Skipped (local override): ${skippedLocal.join(', ')}`);
      }
      if (notFound.length > 0) {
        logger.warn(`[Secret Manager] Not found in GSM: ${notFound.join(', ')}`);
        logger.info(
          `[Secret Manager] Create missing secrets, e.g.: gcloud secrets create SMTP_HOST --data-file=- --project=${this.projectId}`
        );
      }
      logger.info('[Secret Manager] ─── Done ───');
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      const stack = error instanceof Error ? error.stack : undefined;
      logger.error(`[Secret Manager] loadSecrets failed: ${message}`);
      if (stack) logger.debug(stack);
      logger.warn('[Secret Manager] Falling back to .env / existing environment variables');
    }

    return loaded;
  }

  /** Fetch only SMTP secrets from Secret Manager */
  async loadSmtpSecrets(): Promise<Record<string, string>> {
    return this.loadSecrets(SMTP_SECRET_NAMES);
  }

  async getSecretValue(secretName: string, envVarName?: string): Promise<string | null> {
    if (!this.isEnabled() || !this.ensureProjectId()) return null;

    try {
      if (!this.client) await this.initializeClient();
      const value = await this.fetchSecretPayload(secretName);
      if (value === null) return null;

      const envVar = envVarName || this.getEnvVarName(secretName);
      if (!process.env[envVar]) {
        process.env[envVar] = value;
      }
      return value;
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : String(error);
      logger.error(`[Secret Manager] getSecretValue(${secretName}) failed: ${message}`);
      return null;
    }
  }

  isReady(): boolean {
    return this.isInitialized;
  }

  hasLoaded(): boolean {
    return this.isLoaded;
  }
}

export const googleSecretManager = new GoogleSecretManagerService();

export async function initializeGoogleSecretManager(
  secretNames?: readonly string[]
): Promise<void> {
  await googleSecretManager.loadSecrets(secretNames ?? SMTP_SECRET_NAMES);
}

export async function initializeSmtpSecretsFromGoogle(): Promise<Record<string, string>> {
  return googleSecretManager.loadSmtpSecrets();
}
