import * as plugins from '../plugins.js';
import * as paths from '../paths.js';

import { Email } from './mta.classes.email.js';
import type { MtaService } from './mta.classes.mta.js';

const readFile = plugins.util.promisify(plugins.fs.readFile);
const writeFile = plugins.util.promisify(plugins.fs.writeFile);
const generateKeyPair = plugins.util.promisify(plugins.crypto.generateKeyPair);

export interface IKeyPaths {
  privateKeyPath: string;
  publicKeyPath: string;
}

export class DKIMCreator {
  private keysDir: string;

  constructor(private metaRef: MtaService, keysDir = paths.keysDir) {
    this.keysDir = keysDir;
  }

  public async getKeyPathsForDomain(domainArg: string): Promise<IKeyPaths> {
    return {
      privateKeyPath: plugins.path.join(this.keysDir, `${domainArg}-private.pem`),
      publicKeyPath: plugins.path.join(this.keysDir, `${domainArg}-public.pem`),
    };
  }

  // Check if a DKIM key is present and creates one and stores it to disk otherwise
  public async handleDKIMKeysForDomain(domainArg: string): Promise<void> {
    try {
      await this.readDKIMKeys(domainArg);
    } catch (error) {
      console.log(`No DKIM keys found for ${domainArg}. Generating...`);
      await this.createAndStoreDKIMKeys(domainArg);
      const dnsValue = await this.getDNSRecordForDomain(domainArg);
      plugins.smartfile.fs.ensureDirSync(paths.dnsRecordsDir);
      plugins.smartfile.memory.toFsSync(JSON.stringify(dnsValue, null, 2), plugins.path.join(paths.dnsRecordsDir, `${domainArg}.dkimrecord.json`));
    }
  }

  public async handleDKIMKeysForEmail(email: Email): Promise<void> {
    const domain = email.from.split('@')[1];
    await this.handleDKIMKeysForDomain(domain);
  }

  // Read DKIM keys from disk
  public async readDKIMKeys(domainArg: string): Promise<{ privateKey: string; publicKey: string }> {
    const keyPaths = await this.getKeyPathsForDomain(domainArg);
    const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([
      readFile(keyPaths.privateKeyPath),
      readFile(keyPaths.publicKeyPath),
    ]);

    // Convert the buffers to strings
    const privateKey = privateKeyBuffer.toString();
    const publicKey = publicKeyBuffer.toString();

    return { privateKey, publicKey };
  }

  // Create a DKIM key pair - changed to public for API access
  public async createDKIMKeys(): Promise<{ privateKey: string; publicKey: string }> {
    const { privateKey, publicKey } = await generateKeyPair('rsa', {
      modulusLength: 2048,
      publicKeyEncoding: { type: 'spki', format: 'pem' },
      privateKeyEncoding: { type: 'pkcs1', format: 'pem' },
    });

    return { privateKey, publicKey };
  }

  // Store a DKIM key pair to disk - changed to public for API access
  public async storeDKIMKeys(
    privateKey: string,
    publicKey: string,
    privateKeyPath: string,
    publicKeyPath: string
  ): Promise<void> {
    await Promise.all([writeFile(privateKeyPath, privateKey), writeFile(publicKeyPath, publicKey)]);
  }

  // Create a DKIM key pair and store it to disk - changed to public for API access
  public async createAndStoreDKIMKeys(domain: string): Promise<void> {
    const { privateKey, publicKey } = await this.createDKIMKeys();
    const keyPaths = await this.getKeyPathsForDomain(domain);
    await this.storeDKIMKeys(
      privateKey,
      publicKey,
      keyPaths.privateKeyPath,
      keyPaths.publicKeyPath
    );
    console.log(`DKIM keys for ${domain} created and stored.`);
  }

  // Changed to public for API access
  public async getDNSRecordForDomain(domainArg: string): Promise<plugins.tsclass.network.IDnsRecord> {
    await this.handleDKIMKeysForDomain(domainArg);
    const keys = await this.readDKIMKeys(domainArg);

    // Remove the PEM header and footer and newlines
    const pemHeader = '-----BEGIN PUBLIC KEY-----';
    const pemFooter = '-----END PUBLIC KEY-----';
    const keyContents = keys.publicKey
      .replace(pemHeader, '')
      .replace(pemFooter, '')
      .replace(/\n/g, '');

    // Now generate the DKIM DNS TXT record
    const dnsRecordValue = `v=DKIM1; h=sha256; k=rsa; p=${keyContents}`;

    return {
      name: `mta._domainkey.${domainArg}`,
      type: 'TXT',
      dnsSecEnabled: null,
      value: dnsRecordValue,
    };
  }
}