import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname } from 'node:path';
import { PrismaService } from '../infra/prisma.service';
import {
  collectOAuthSslHostnames,
  parseHostnameFromPublicUrl,
  resolveRegistrableDomain
} from '../infra/oauth-ssl-sans.util';

const OAUTH_SSL_SANS_SETTING_KEY = 'OAUTH_SSL_SANS';

@Injectable()
export class OAuthSslSansService implements OnModuleInit {
  private readonly logger = new Logger(OAuthSslSansService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly config: ConfigService
  ) {}

  async onModuleInit() {
    try {
      await this.syncFromDatabase();
    } catch (error) {
      this.logger.warn(
        `Не удалось синхронизировать SAN из OAuth redirect URI: ${error instanceof Error ? error.message : 'ошибка'}`
      );
    }
  }

  async syncFromDatabase(): Promise<string[]> {
    const clients = await this.prisma.oAuthClient.findMany({
      where: { isActive: true },
      select: { redirectUris: true }
    });
    const redirectUris = clients.flatMap((client) => client.redirectUris);
    const hostnames = collectOAuthSslHostnames(redirectUris, this.buildExcludedHostnames());

    await this.persistHostnames(hostnames);
    return hostnames;
  }

  private buildExcludedHostnames(): Set<string> {
    const excluded = new Set<string>();

    for (const envKey of ['DOMAIN_API', 'DOMAIN_FRONTEND', 'DOMAIN_DOCS', 'DOMAIN_WS', 'DOMAIN_MINIO', 'DOMAIN_MINIO_CONSOLE']) {
      const value = this.config.get<string>(envKey)?.trim().toLowerCase();
      if (value) excluded.add(value);
    }

    for (const url of [
      this.config.get<string>('PUBLIC_API_URL'),
      this.config.get<string>('PUBLIC_FRONTEND_URL'),
      this.config.get<string>('PUBLIC_DOCS_URL'),
      this.config.get<string>('PUBLIC_WS_URL')
    ]) {
      const host = parseHostnameFromPublicUrl(url);
      if (host) excluded.add(host);
    }

    const idpHosts = [...excluded];
    for (const host of idpHosts) {
      excluded.add(resolveRegistrableDomain(host));
      const parts = host.split('.');
      if (parts.length >= 3) {
        excluded.add(parts.slice(-2).join('.'));
      }
    }

    return excluded;
  }

  private async persistHostnames(hostnames: string[]) {
    const serialized = hostnames.join(',');
    await this.prisma.systemSetting.upsert({
      where: { key: OAUTH_SSL_SANS_SETTING_KEY },
      create: {
        key: OAUTH_SSL_SANS_SETTING_KEY,
        value: serialized,
        description: 'DNS-имена из redirect_uri OAuth-приложений для SAN сертификата (авто)',
        isSecret: false
      },
      update: {
        value: serialized,
        description: 'DNS-имена из redirect_uri OAuth-приложений для SAN сертификата (авто)'
      }
    });

    const targetFile =
      this.config.get<string>('OAUTH_SSL_SANS_FILE')?.trim() ||
      '/data/nginx-certs/oauth-redirect-sans.txt';
    const body = `${hostnames.join('\n')}\n`;
    await mkdir(dirname(targetFile), { recursive: true });
    await writeFile(targetFile, body, 'utf8');
    this.logger.log(`OAuth SSL SAN обновлён (${hostnames.length}): ${hostnames.join(', ') || '—'}`);
  }
}
