import { Config, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import * as crypto from 'crypto';

interface SecretEnvelope {
  v: 1;
  alg: 'aes-256-gcm';
  iv: string;
  tag: string;
  ct: string;
}

@Provide()
@Scope(ScopeEnum.Singleton)
export class SecretCryptoService {
  @Config('module.user.jwt.secret')
  jwtSecret: string;

  private readonly algorithm = 'aes-256-gcm' as const;
  private readonly ivLength = 12;

  encryptText(plainText: string): string {
    const iv = crypto.randomBytes(this.ivLength);
    const cipher = crypto.createCipheriv(this.algorithm, this.getKey(), iv);
    const ct = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]);
    const envelope: SecretEnvelope = {
      v: 1,
      alg: this.algorithm,
      iv: iv.toString('base64'),
      tag: cipher.getAuthTag().toString('base64'),
      ct: ct.toString('base64'),
    };

    return Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64');
  }

  decryptText(encryptedText: string): string {
    const envelope = this.parseEnvelope(encryptedText);
    const decipher = crypto.createDecipheriv(
      this.algorithm,
      this.getKey(),
      Buffer.from(envelope.iv, 'base64')
    );
    decipher.setAuthTag(Buffer.from(envelope.tag, 'base64'));
    const decrypted = Buffer.concat([
      decipher.update(Buffer.from(envelope.ct, 'base64')),
      decipher.final(),
    ]);

    return decrypted.toString('utf8');
  }

  encryptJson(value: unknown): string {
    return this.encryptText(JSON.stringify(value));
  }

  decryptJson<T = unknown>(encryptedText: string): T {
    return JSON.parse(this.decryptText(encryptedText)) as T;
  }

  private getKey(): Buffer {
    const raw = process.env.NETA_SECRET_KEY || process.env.APP_SECRET || this.jwtSecret;
    if (!raw) {
      throw new Error('NETA_SECRET_KEY, APP_SECRET, or module.user.jwt.secret is required for secret crypto');
    }
    return crypto.createHash('sha256').update(raw).digest();
  }

  private parseEnvelope(encryptedText: string): SecretEnvelope {
    const decoded = Buffer.from(encryptedText, 'base64').toString('utf8');
    const envelope = JSON.parse(decoded) as Partial<SecretEnvelope>;
    if (
      envelope.v !== 1 ||
      envelope.alg !== this.algorithm ||
      typeof envelope.iv !== 'string' ||
      typeof envelope.tag !== 'string' ||
      typeof envelope.ct !== 'string'
    ) {
      throw new Error('Invalid secret envelope');
    }

    return envelope as SecretEnvelope;
  }
}
