import { BadRequestException, Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { authenticator } from 'otplib';

import { EncryptionService } from '../infra/encryption.service';
import { PrismaService } from '../infra/prisma.service';
import { SettingsService } from './settings.service';
import { AccessService } from './access.service';

@Injectable()
export class TotpService {
  private readonly totp = authenticator.clone({ window: 2 });

  constructor(
    private readonly prisma: PrismaService,
    private readonly encryption: EncryptionService,
    private readonly settings: SettingsService,
    private readonly access: AccessService
  ) {}

  async getStatus(userId: string) {
    const record = await this.prisma.userTotpSecret.findUnique({ where: { userId } });
    return { isEnabled: Boolean(record?.isEnabled) };
  }

  async isEnabledForUser(userId: string) {
    const record = await this.prisma.userTotpSecret.findUnique({ where: { userId } });
    return Boolean(record?.isEnabled);
  }

  async setup(userId: string) {
    await this.ensureUserExists(userId);

    const issuer = await this.resolveIssuer();
    const user = await this.prisma.user.findUnique({ where: { id: userId } });
    const accountLabel = this.buildAccountLabel(user, userId);
    const existing = await this.prisma.userTotpSecret.findUnique({ where: { userId } });

    if (existing && !existing.isEnabled) {
      const secret = this.tryDecryptSecret(existing.secretEnc);
      if (secret) {
        return {
          secret,
          otpauthUrl: this.totp.keyuri(accountLabel, issuer, secret)
        };
      }
    }

    const secret = this.totp.generateSecret();
    const secretEnc = this.encryption.encrypt(secret);
    this.assertSecretRoundTrip(secret, secretEnc);

    const otpauthUrl = this.totp.keyuri(accountLabel, issuer, secret);

    await this.prisma.userTotpSecret.upsert({
      where: { userId },
      create: {
        userId,
        secretEnc,
        isEnabled: false
      },
      update: {
        secretEnc,
        isEnabled: false
      }
    });

    return { secret, otpauthUrl };
  }

  async enable(userId: string, code: string) {
    const record = await this.prisma.userTotpSecret.findUnique({ where: { userId } });
    if (!record) {
      throw new BadRequestException('Сначала начните настройку приложения-аутентификатора');
    }
    if (record.isEnabled) {
      return { isEnabled: true };
    }
    this.assertValidCode(record.secretEnc, code);
    await this.prisma.userTotpSecret.update({
      where: { userId },
      data: { isEnabled: true }
    });
    return { isEnabled: true };
  }

  async disable(userId: string, code: string) {
    const record = await this.prisma.userTotpSecret.findUnique({ where: { userId } });
    if (!record?.isEnabled) {
      throw new BadRequestException('Двухфакторная аутентификация не включена');
    }
    this.assertValidCode(record.secretEnc, code);
    await this.prisma.userTotpSecret.delete({ where: { userId } });
    return { isEnabled: false };
  }

  async adminDisable(actorUserId: string, targetUserId: string) {
    await this.access.assertSuperAdmin(actorUserId);
    await this.ensureUserExists(targetUserId);

    const record = await this.prisma.userTotpSecret.findUnique({ where: { userId: targetUserId } });
    if (!record?.isEnabled) {
      throw new BadRequestException('Двухфакторная аутентификация не включена');
    }

    await this.prisma.userTotpSecret.delete({ where: { userId: targetUserId } });
    return { isEnabled: false };
  }

  async verifyEnabledCode(userId: string, code: string) {
    const record = await this.prisma.userTotpSecret.findUnique({ where: { userId } });
    if (!record?.isEnabled) {
      return false;
    }
    return this.isValidCode(record.secretEnc, code);
  }

  private async resolveIssuer() {
    const projectName = (await this.settings.getValue('PROJECT_NAME', 'MVK ID')).trim();
    return projectName || 'MVK ID';
  }

  private buildAccountLabel(
    user: { email: string | null; phone: string | null; username: string | null } | null,
    userId: string
  ) {
    return user?.email ?? user?.phone ?? user?.username ?? userId;
  }

  private tryDecryptSecret(secretEnc: string) {
    try {
      const secret = this.encryption.decrypt(secretEnc).trim();
      return secret.length > 0 ? secret : null;
    } catch {
      return null;
    }
  }

  private assertSecretRoundTrip(secret: string, secretEnc: string) {
    const decrypted = this.encryption.decrypt(secretEnc);
    if (decrypted !== secret) {
      throw new InternalServerErrorException('Не удалось сохранить секрет аутентификатора');
    }

    const probeToken = this.totp.generate(secret);
    if (!this.totp.verify({ token: probeToken, secret: decrypted })) {
      throw new InternalServerErrorException('Не удалось проверить секрет аутентификатора');
    }
  }

  private assertValidCode(secretEnc: string, code: string) {
    if (!this.isValidCode(secretEnc, code)) {
      throw new BadRequestException(
        'Неверный код аутентификатора. Убедитесь, что отсканирован последний QR-код, и повторите настройку при необходимости.'
      );
    }
  }

  private isValidCode(secretEnc: string, code: string) {
    const normalized = code.replace(/\s/g, '');
    if (!/^\d{6}$/.test(normalized)) {
      return false;
    }

    const secret = this.tryDecryptSecret(secretEnc);
    if (!secret) {
      return false;
    }

    return this.totp.verify({ token: normalized, secret });
  }

  private async ensureUserExists(userId: string) {
    const user = await this.prisma.user.findUnique({ where: { id: userId } });
    if (!user || user.deletedAt) {
      throw new NotFoundException('Пользователь не найден');
    }
  }
}
