import type {
  IEmailRoute,
  IUnifiedEmailServerOptions,
} from '@push.rocks/smartmta';
import * as plugins from '../plugins.js';
import type * as interfaces from '../../ts_interfaces/index.js';

type TSyncRequest = interfaces.requests.IReq_SyncWorkAppMailIdentity['request'];

interface IStoredWorkAppMailIdentity extends interfaces.data.IWorkAppMailIdentity {
  smtpPassword: string;
}

interface IStoredWorkAppMailState {
  version: 1;
  identities: IStoredWorkAppMailIdentity[];
}

export class WorkAppMailManager {
  private readonly storageKey = '/workhosters/mail-identities.json';

  constructor(private dcRouterRef: any) {}

  public async listMailIdentities(
    ownership?: Partial<interfaces.data.IWorkAppMailOwnership>,
  ): Promise<interfaces.data.IWorkAppMailIdentity[]> {
    const identities = await this.readStoredIdentities();
    return identities
      .filter((identity) => this.matchesOwnership(identity.ownership, ownership))
      .map((identity) => this.toPublicIdentity(identity));
  }

  public async syncMailIdentity(
    request: TSyncRequest,
    createdBy: string,
  ): Promise<interfaces.data.IWorkAppMailIdentitySyncResult> {
    if (!this.dcRouterRef.options.emailConfig) {
      return { success: false, message: 'Email server is not configured' };
    }

    const ownership = this.normalizeOwnership(request.ownership);
    const domain = this.normalizeDomain(request.domain);
    const localPart = this.normalizeLocalPart(request.localPart);
    const address = `${localPart}@${domain}`;
    const externalKey = this.buildExternalKey(ownership, address);
    const identities = await this.readStoredIdentities();
    const existingIndex = identities.findIndex((identity) => identity.externalKey === externalKey);

    if (request.delete) {
      if (existingIndex < 0) {
        return { success: true, action: 'unchanged' };
      }
      const [deletedIdentity] = identities.splice(existingIndex, 1);
      await this.writeStoredIdentities(identities);
      await this.applyStoredIdentitiesToRuntime(identities);
      return {
        success: true,
        action: 'deleted',
        identity: this.toPublicIdentity(deletedIdentity),
      };
    }

    await this.ensureEmailDomainConfigured(domain);

    const existingIdentity = existingIndex >= 0 ? identities[existingIndex] : undefined;
    const now = Date.now();
    const smtpPassword = existingIdentity && !request.resetSmtpPassword
      ? existingIdentity.smtpPassword
      : this.generateSmtpPassword();
    const identity: IStoredWorkAppMailIdentity = {
      id: existingIdentity?.id || plugins.smartunique.shortId(),
      externalKey,
      ownership,
      address,
      localPart,
      domain,
      enabled: request.enabled ?? existingIdentity?.enabled ?? true,
      displayName: request.displayName ?? existingIdentity?.displayName,
      inbound: this.normalizeInboundRoute(request.inbound ?? existingIdentity?.inbound),
      smtp: {
        enabled: request.smtpEnabled ?? existingIdentity?.smtp.enabled ?? true,
        username: existingIdentity?.smtp.username || this.buildSmtpUsername(externalKey),
      },
      createdAt: existingIdentity?.createdAt || now,
      updatedAt: now,
      createdBy: existingIdentity?.createdBy || createdBy,
      smtpPassword,
    };

    if (existingIndex >= 0) {
      identities[existingIndex] = identity;
    } else {
      identities.push(identity);
    }

    await this.writeStoredIdentities(identities);
    await this.applyStoredIdentitiesToRuntime(identities);

    const response: interfaces.data.IWorkAppMailIdentitySyncResult = {
      success: true,
      action: existingIndex >= 0 ? 'updated' : 'created',
      identity: this.toPublicIdentity(identity),
    };

    if (existingIndex < 0 || request.resetSmtpPassword) {
      response.smtpCredentials = this.buildSmtpCredentials(identity);
    }

    return response;
  }

  public async applyStoredIdentitiesToEmailConfig<TConfig extends IUnifiedEmailServerOptions>(
    emailConfig: TConfig,
  ): Promise<TConfig> {
    const identities = await this.readStoredIdentities();
    return this.mergeIdentitiesIntoEmailConfig(emailConfig, identities);
  }

  public async applyStoredIdentitiesToRuntime(
    identities = undefined as IStoredWorkAppMailIdentity[] | undefined,
  ): Promise<void> {
    const emailConfig = this.dcRouterRef.options.emailConfig as IUnifiedEmailServerOptions | undefined;
    if (!emailConfig) return;

    const nextConfig = this.mergeIdentitiesIntoEmailConfig(
      emailConfig,
      identities || await this.readStoredIdentities(),
    );

    this.dcRouterRef.options.emailConfig = nextConfig;
    if (this.dcRouterRef.emailServer) {
      this.dcRouterRef.emailServer.updateOptions({ auth: nextConfig.auth });
      await this.dcRouterRef.updateEmailRoutes(nextConfig.routes);
    }
  }

  private async readStoredIdentities(): Promise<IStoredWorkAppMailIdentity[]> {
    const storedData = await this.dcRouterRef.storageManager.get(this.storageKey);
    if (!storedData) return [];
    const parsed = JSON.parse(storedData) as IStoredWorkAppMailState | IStoredWorkAppMailIdentity[];
    return Array.isArray(parsed) ? parsed : parsed.identities || [];
  }

  private async writeStoredIdentities(identities: IStoredWorkAppMailIdentity[]): Promise<void> {
    const state: IStoredWorkAppMailState = {
      version: 1,
      identities,
    };
    await this.dcRouterRef.storageManager.set(this.storageKey, JSON.stringify(state, null, 2));
  }

  private mergeIdentitiesIntoEmailConfig<TConfig extends IUnifiedEmailServerOptions>(
    emailConfig: TConfig,
    identities: IStoredWorkAppMailIdentity[],
  ): TConfig {
    const generatedRoutes = identities
      .filter((identity) => identity.enabled && identity.inbound?.enabled)
      .map((identity) => this.buildInboundRoute(identity));
    const configuredRoutes = (emailConfig.routes || [])
      .filter((route) => !this.isManagedMailRouteName(route.name));
    const generatedUsers = identities
      .filter((identity) => identity.enabled && identity.smtp.enabled)
      .map((identity) => ({
        username: identity.smtp.username,
        password: identity.smtpPassword,
      }));
    const configuredUsers = (emailConfig.auth?.users || [])
      .filter((user) => !this.isManagedSmtpUsername(user.username));

    return {
      ...emailConfig,
      routes: [...configuredRoutes, ...generatedRoutes],
      auth: {
        ...(emailConfig.auth || {}),
        users: [...configuredUsers, ...generatedUsers],
      },
    };
  }

  private buildInboundRoute(identity: IStoredWorkAppMailIdentity): IEmailRoute {
    const inbound = identity.inbound!;
    return {
      name: this.buildRouteName(identity.externalKey),
      priority: 1000,
      match: {
        recipients: identity.address,
      },
      action: {
        type: 'forward',
        forward: {
          host: inbound.targetHost,
          port: inbound.targetPort,
          preserveHeaders: inbound.preserveHeaders ?? true,
          addHeaders: {
            'X-Dcrouter-WorkHoster-Type': identity.ownership.workHosterType,
            'X-Dcrouter-WorkHoster-Id': identity.ownership.workHosterId,
            'X-Dcrouter-WorkApp-Id': identity.ownership.workAppId,
            ...(inbound.addHeaders || {}),
          },
        },
      },
    };
  }

  private async ensureEmailDomainConfigured(domain: string): Promise<void> {
    const emailConfig = this.dcRouterRef.options.emailConfig as IUnifiedEmailServerOptions | undefined;
    if (emailConfig?.domains?.some((domainConfig) => domainConfig.domain.toLowerCase() === domain)) {
      return;
    }

    const emailDomainManager = this.dcRouterRef.emailDomainManager;
    if (!emailDomainManager) {
      throw new Error(`Email domain is not configured: ${domain}`);
    }

    if (await emailDomainManager.getByDomain(domain)) {
      await emailDomainManager.syncManagedDomainsToRuntime();
      return;
    }

    await emailDomainManager.ensureEmailDomainForDomainName(domain);
  }

  private normalizeOwnership(
    ownership: interfaces.data.IWorkAppMailOwnership,
  ): interfaces.data.IWorkAppMailOwnership {
    const workHosterType = ownership.workHosterType;
    const workHosterId = ownership.workHosterId?.trim();
    const workAppId = ownership.workAppId?.trim();
    if (!['onebox', 'cloudly', 'custom'].includes(workHosterType)) {
      throw new Error(`Invalid WorkHoster type: ${workHosterType}`);
    }
    if (!workHosterId) throw new Error('workHosterId is required');
    if (!workAppId) throw new Error('workAppId is required');
    return { workHosterType, workHosterId, workAppId };
  }

  private normalizeDomain(domain: string): string {
    const normalized = domain?.trim().toLowerCase();
    if (!normalized || normalized.includes('@') || !normalized.includes('.')) {
      throw new Error(`Invalid email domain: ${domain}`);
    }
    return normalized;
  }

  private normalizeLocalPart(localPart: string): string {
    const normalized = localPart?.trim().toLowerCase();
    if (!normalized || normalized.includes('@') || /\s/.test(normalized)) {
      throw new Error(`Invalid email local part: ${localPart}`);
    }
    return normalized;
  }

  private normalizeInboundRoute(
    inbound?: interfaces.data.IWorkAppMailInboundRoute,
  ): interfaces.data.IWorkAppMailInboundRoute | undefined {
    if (!inbound) return undefined;
    if (!inbound.enabled) {
      return { ...inbound, enabled: false };
    }
    const targetHost = inbound.targetHost?.trim();
    const targetPort = Number(inbound.targetPort);
    if (!targetHost) throw new Error('inbound.targetHost is required when inbound routing is enabled');
    if (!Number.isInteger(targetPort) || targetPort < 1 || targetPort > 65535) {
      throw new Error(`Invalid inbound.targetPort: ${inbound.targetPort}`);
    }
    return {
      ...inbound,
      targetHost,
      targetPort,
    };
  }

  private matchesOwnership(
    ownership: interfaces.data.IWorkAppMailOwnership,
    filter?: Partial<interfaces.data.IWorkAppMailOwnership>,
  ): boolean {
    if (!filter) return true;
    if (filter.workHosterType && filter.workHosterType !== ownership.workHosterType) return false;
    if (filter.workHosterId && filter.workHosterId !== ownership.workHosterId) return false;
    if (filter.workAppId && filter.workAppId !== ownership.workAppId) return false;
    return true;
  }

  private buildExternalKey(
    ownership: interfaces.data.IWorkAppMailOwnership,
    address: string,
  ): string {
    return [
      ownership.workHosterType,
      ownership.workHosterId,
      ownership.workAppId,
      address,
    ].join(':');
  }

  private buildSmtpUsername(externalKey: string): string {
    return `workapp-${this.hashExternalKey(externalKey).slice(0, 24)}`;
  }

  private buildRouteName(externalKey: string): string {
    return `workapp-mail-${this.hashExternalKey(externalKey).slice(0, 32)}`;
  }

  private hashExternalKey(externalKey: string): string {
    return plugins.crypto.createHash('sha256').update(externalKey).digest('hex');
  }

  private generateSmtpPassword(): string {
    return plugins.crypto.randomBytes(24).toString('base64url');
  }

  private isManagedMailRouteName(routeName: string): boolean {
    return routeName.startsWith('workapp-mail-');
  }

  private isManagedSmtpUsername(username: string): boolean {
    return username.startsWith('workapp-');
  }

  private buildSmtpCredentials(
    identity: IStoredWorkAppMailIdentity,
  ): interfaces.data.IWorkAppMailCredentials {
    return {
      username: identity.smtp.username,
      password: identity.smtpPassword,
      host: this.dcRouterRef.options.emailConfig?.outbound?.hostname
        || this.dcRouterRef.options.emailConfig?.hostname,
      ports: {
        smtp: this.dcRouterRef.options.emailConfig?.ports?.includes(25) ? 25 : undefined,
        submission: this.dcRouterRef.options.emailConfig?.ports?.includes(587) ? 587 : undefined,
        smtps: this.dcRouterRef.options.emailConfig?.ports?.includes(465) ? 465 : undefined,
      },
    };
  }

  private toPublicIdentity(
    identity: IStoredWorkAppMailIdentity,
  ): interfaces.data.IWorkAppMailIdentity {
    const { smtpPassword, ...publicIdentity } = identity;
    return publicIdentity;
  }
}
