import { BadRequestException, Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { credKeyFromEnv, sealSecret, openSecret, type SealedSecret } from './secret-crypto';

/** A provider config is an opaque JSON bag; each provider knows its own shape. */
export type ProviderConfig = Record<string, unknown>;

export interface CredentialStatus {
  configured: boolean;
  enabled?: boolean;
  hints?: Record<string, unknown>;
}

/** Non-secret display fields surfaced by `status` — NEVER the secret itself. */
function hintsFor(providerType: string, config: ProviderConfig): Record<string, unknown> {
  if (providerType === 'TWILIO_SMS') {
    const sid = String(config.accountSid ?? '');
    return { fromNumber: config.fromNumber ?? null, sidLast4: sid.slice(-4) };
  }
  if (providerType === 'SMTP') {
    return { host: config.host ?? null, port: config.port ?? null, user: config.user ?? null, fromEmail: config.fromEmail ?? null, fromName: config.fromName ?? null };
  }
  return {};
}

/**
 * The tenant's BYO integration credentials, one row per (scope, providerType). The secret is
 * sealed with AES-256-GCM under the platform key before it touches the DB; only non-secret
 * `displayHints` are ever read back. `resolve` decrypts on demand at send time; `status` never
 * returns the secret. Fail-closed: no platform key ⇒ upsert throws (we refuse to store plaintext).
 */
@Injectable()
export class ProviderCredentialService {
  /** Overridable in tests; defaults to the process env (holds IIOS_CRED_KEY). */
  env: NodeJS.ProcessEnv = process.env;

  constructor(private readonly prisma: PrismaService) {}

  async upsert(scopeId: string, providerType: string, config: ProviderConfig, opts?: { enabled?: boolean }): Promise<void> {
    const key = credKeyFromEnv(this.env);
    if (!key) throw new BadRequestException('IIOS_CRED_KEY is not configured — cannot store integration credentials');
    const sealed = sealSecret(JSON.stringify(config), key);
    const enabled = opts?.enabled ?? true;
    const displayHints = hintsFor(providerType, config) as Prisma.InputJsonValue;
    await this.prisma.iiosProviderCredential.upsert({
      where: { scopeId_providerType: { scopeId, providerType } },
      create: { scopeId, providerType, ...sealed, displayHints, enabled },
      update: { ...sealed, displayHints, enabled },
    });
  }

  /** Decrypt the config for send-time use. Null when unconfigured, disabled, or no key. */
  async resolve(scopeId: string, providerType: string): Promise<ProviderConfig | null> {
    const row = await this.prisma.iiosProviderCredential.findUnique({
      where: { scopeId_providerType: { scopeId, providerType } },
    });
    if (!row || !row.enabled) return null;
    const key = credKeyFromEnv(this.env);
    if (!key) return null;
    const sealed: SealedSecret = { cipherText: row.cipherText, iv: row.iv, authTag: row.authTag, keyVersion: row.keyVersion };
    return JSON.parse(openSecret(sealed, key)) as ProviderConfig;
  }

  /** Masked view for the admin UI — configured + hints, never the secret. */
  async status(scopeId: string, providerType: string): Promise<CredentialStatus> {
    const row = await this.prisma.iiosProviderCredential.findUnique({
      where: { scopeId_providerType: { scopeId, providerType } },
    });
    if (!row) return { configured: false };
    return { configured: true, enabled: row.enabled, hints: (row.displayHints ?? {}) as Record<string, unknown> };
  }
}
