import { BadRequestException, Injectable, Logger } from '@nestjs/common';
import nodemailer from 'nodemailer';
import { SettingsService } from '../domain/settings.service';
import { SmsService } from './sms.service';

type MessagingChannel = 'email' | 'sms';

interface VerificationPayload {
  channel: MessagingChannel;
  target: string;
  code: string;
  expiryMinutes: number;
}

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

  constructor(
    private readonly settings: SettingsService,
    private readonly smsService: SmsService
  ) {}

  async sendVerificationCode(payload: VerificationPayload): Promise<void> {
    if (payload.channel === 'email') {
      await this.sendEmailCode(payload);
      return;
    }
    await this.sendSmsCode(payload);
  }

  async sendTestDelivery(channel: MessagingChannel, target: string) {
    const expiryMinutes = await this.settings.getNumber('OTP_EXPIRY_MINUTES', 10);
    const code = String(Math.floor(100000 + Math.random() * 900000));
    await this.sendVerificationCode({ channel, target, code, expiryMinutes });
    return {
      sent: true,
      channel,
      target,
      message: `Тестовое сообщение отправлено на ${this.maskTarget(target, channel)}`
    };
  }

  private async sendEmailCode(payload: VerificationPayload) {
    const enabled = await this.settings.getBoolean('EMAIL_ENABLED', false);
    const provider = (await this.settings.getValue('EMAIL_PROVIDER', 'smtp')).trim().toLowerCase();
    const host = (await this.settings.getValue('EMAIL_SMTP_HOST', '')).trim();
    const fromAddress = (await this.settings.getValue('EMAIL_FROM_ADDRESS', '')).trim();
    const smtpConfigured = Boolean(host && fromAddress);

    if (provider === 'console') {
      this.logConsoleFallback('email', payload, 'выбран режим «Только лог» (EMAIL_PROVIDER=console)');
      return;
    }

    if (!enabled && !smtpConfigured) {
      this.logConsoleFallback(
        'email',
        payload,
        'EMAIL_ENABLED=false — включите Email в админке и нажмите «Сохранить»'
      );
      return;
    }

    if (!enabled && smtpConfigured) {
      this.logger.warn('[EMAIL] EMAIL_ENABLED=false, но SMTP заполнен — отправляем письмо');
    }

    if (provider !== 'smtp') {
      throw new BadRequestException(`Неизвестный email-провайдер: ${provider}`);
    }

    const port = await this.settings.getNumber('EMAIL_SMTP_PORT', 587);
    const secure = await this.settings.getBoolean('EMAIL_SMTP_SECURE', false);
    const user = (await this.settings.getValue('EMAIL_SMTP_USER', '')).trim();
    const password = await this.settings.getValue('EMAIL_SMTP_PASSWORD', '');
    const fromName = (await this.settings.getValue('EMAIL_FROM_NAME', '')).trim();
    const projectName = await this.settings.getValue('PROJECT_NAME', 'MVK ID');

    if (!host || !fromAddress) {
      throw new BadRequestException('Email не настроен: укажите SMTP-хост и адрес отправителя в админке');
    }

    const from = fromName ? `"${fromName}" <${fromAddress}>` : fromAddress;
    const subject = `${projectName}: код подтверждения`;
    const text = this.buildCodeText(projectName, payload.code, payload.expiryMinutes);
    const html = this.buildCodeHtml(projectName, payload.code, payload.expiryMinutes);

    const transporter = nodemailer.createTransport({
      host,
      port,
      secure,
      auth: user ? { user, pass: password } : undefined,
      tls: secure ? undefined : { rejectUnauthorized: true }
    });

    try {
      await transporter.sendMail({
        from,
        to: payload.target,
        subject,
        text,
        html
      });
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Неизвестная ошибка SMTP';
      this.logger.error(`SMTP ошибка для ${payload.target}: ${message}`);
      throw new BadRequestException(`Не удалось отправить письмо: ${message}`);
    }
  }

  private async sendSmsCode(payload: VerificationPayload) {
    const enabled = await this.settings.getBoolean('SMS_ENABLED', false);
    const provider = (await this.settings.getValue('SMS_PROVIDER', 'smsru')).trim().toLowerCase();

    if (!enabled || provider === 'console') {
      this.logConsoleFallback(
        'sms',
        payload,
        !enabled
          ? 'SMS_ENABLED=false — включите SMS в админке и нажмите «Сохранить»'
          : 'выбран режим «Только лог» (SMS_PROVIDER=console)'
      );
      return;
    }

    const projectName = await this.settings.getValue('PROJECT_NAME', 'MVK ID');
    const phone = this.normalizePhone(payload.target);
    const text =
      provider === 'huawei_modem'
        ? await this.buildHuaweiSmsText(projectName, payload.code, payload.expiryMinutes)
        : `${projectName}: код ${payload.code}. Действует ${payload.expiryMinutes} мин.`;

    if (provider === 'smsru') {
      await this.sendViaSmsRu(phone, text);
      return;
    }

    if (provider === 'smsc') {
      await this.sendViaSmsc(phone, text);
      return;
    }

    if (provider === 'huawei_modem') {
      const sent = await this.smsService.sendOtp(phone, text);
      if (!sent) {
        throw new BadRequestException('Не удалось отправить SMS через локальный шлюз Huawei');
      }
      return;
    }

    throw new BadRequestException(`Неизвестный SMS-провайдер: ${provider}`);
  }

  private async sendViaSmsRu(phone: string, text: string) {
    const apiId = (await this.settings.getValue('SMS_API_KEY', '')).trim();
    const sender = (await this.settings.getValue('SMS_SENDER', '')).trim();

    if (!apiId) {
      throw new BadRequestException('SMS не настроен: укажите API-ключ sms.ru в админке');
    }

    const params = new URLSearchParams({
      api_id: apiId,
      to: phone,
      msg: text,
      json: '1'
    });
    if (sender) params.set('from', sender);

    const response = await fetch(`https://sms.ru/sms/send?${params.toString()}`);
    const body = (await response.json()) as { status?: string; status_code?: number; status_text?: string };

    if (!response.ok || body.status !== 'OK') {
      const reason = body.status_text ?? `HTTP ${response.status}`;
      throw new BadRequestException(`sms.ru: ${reason}`);
    }
  }

  private async sendViaSmsc(phone: string, text: string) {
    const login = (await this.settings.getValue('SMS_LOGIN', '')).trim();
    const password = (await this.settings.getValue('SMS_PASSWORD', '')).trim();
    const sender = (await this.settings.getValue('SMS_SENDER', '')).trim();

    if (!login || !password) {
      throw new BadRequestException('SMS не настроен: укажите логин и пароль smsc.ru в админке');
    }

    const params = new URLSearchParams({
      login,
      psw: password,
      phones: phone,
      mes: text,
      fmt: '3',
      charset: 'utf-8'
    });
    if (sender) params.set('sender', sender);

    const response = await fetch(`https://smsc.ru/sys/send.php?${params.toString()}`);
    const body = (await response.json()) as { error?: string; error_code?: number; id?: number };

    if (!response.ok || body.error) {
      throw new BadRequestException(`smsc.ru: ${body.error ?? `HTTP ${response.status}`}`);
    }
  }

  private async buildHuaweiSmsText(projectName: string, code: string, expiryMinutes: number) {
    const template = (await this.settings.getValue('SMS_GATEWAY_MESSAGE_TEMPLATE', '')).trim();
    const fallback = `${projectName}: код ${code}. Действует ${expiryMinutes} мин.`;

    if (!template) {
      return fallback;
    }

    const rendered = this.applySmsTemplate(template, {
      appname: projectName,
      code,
      expiry: String(expiryMinutes),
      expiryminutes: String(expiryMinutes)
    });

    return rendered.trim() || fallback;
  }

  private applySmsTemplate(template: string, variables: Record<string, string>) {
    return template.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (match, rawKey: string) => {
      const key = rawKey.toLowerCase();
      return variables[key] ?? match;
    });
  }

  private normalizePhone(value: string) {
    const digits = value.replace(/\D/g, '');
    if (digits.length === 11 && digits.startsWith('8')) return `7${digits.slice(1)}`;
    if (digits.length === 10) return `7${digits}`;
    return digits;
  }

  private buildCodeText(projectName: string, code: string, expiryMinutes: number) {
    return [
      `Ваш код подтверждения ${projectName}: ${code}`,
      '',
      `Код действителен ${expiryMinutes} мин.`,
      'Если вы не запрашивали код — проигнорируйте это сообщение.'
    ].join('\n');
  }

  private buildCodeHtml(projectName: string, code: string, expiryMinutes: number) {
    return `
      <div style="font-family:Arial,sans-serif;line-height:1.5;color:#101828">
        <p>Ваш код подтверждения <strong>${projectName}</strong>:</p>
        <p style="font-size:28px;font-weight:700;letter-spacing:4px">${code}</p>
        <p>Код действителен ${expiryMinutes} мин.</p>
        <p style="color:#667085;font-size:13px">Если вы не запрашивали код — проигнорируйте это письмо.</p>
      </div>
    `.trim();
  }

  private logConsoleFallback(channel: MessagingChannel, payload: VerificationPayload, reason: string) {
    this.logger.warn(`[${channel.toUpperCase()}] Код для ${payload.target}: ${payload.code} (${reason})`);
  }

  private maskTarget(target: string, channel: MessagingChannel) {
    if (channel === 'email' && target.includes('@')) {
      const [name, domain] = target.split('@');
      return `${name.slice(0, 1)}***@${domain}`;
    }
    const digits = target.replace(/\D/g, '');
    return `***${digits.slice(-4)}`;
  }
}
