import {
  BadRequestException,
  HttpException,
  HttpStatus,
  Injectable,
  Logger,
} from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { User } from 'src/users/user.entity';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { VerificationStoreService } from './verification-store.service';
import { SmsService } from './sms.service';
import { RefreshTokenStoreService } from './refresh-token-store.service';
import { jwtConstants } from './constants';
import {
  SMS_PER_IP_MAX,
  SMS_PER_PHONE_MAX,
} from './verification.constants';

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

  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
    private configService: ConfigService,
    private verificationStore: VerificationStoreService,
    private smsService: SmsService,
    private refreshTokenStore: RefreshTokenStoreService,
  ) {}

  /** Отправить код верификации на номер. Лимиты: 1/60с на номер, 3/15мин на номер, 10/15мин на IP. */
  async sendVerificationCode(phone: string, ip: string): Promise<{ success: true }> {
    const normalizedPhone = this.normalizePhone(phone);
    if (!normalizedPhone) {
      throw new BadRequestException('Некорректный номер телефона');
    }

    if (!this.verificationStore.canSendToPhone(normalizedPhone)) {
      throw new HttpException(
        'Повторная отправка кода возможна не чаще 1 раза в 60 секунд',
        HttpStatus.TOO_MANY_REQUESTS,
      );
    }
    if (this.verificationStore.getSendCountByPhone(normalizedPhone) >= SMS_PER_PHONE_MAX) {
      throw new HttpException(
        `Не более ${SMS_PER_PHONE_MAX} SMS на номер за 15 минут`,
        HttpStatus.TOO_MANY_REQUESTS,
      );
    }
    if (this.verificationStore.getSendCountByIp(ip) >= SMS_PER_IP_MAX) {
      throw new HttpException(
        `Превышен лимит запросов с вашего IP (${SMS_PER_IP_MAX} за 15 минут)`,
        HttpStatus.TOO_MANY_REQUESTS,
      );
    }

    const code = this.generateCode();
    this.verificationStore.recordSend(normalizedPhone, ip, code);
    await this.smsService.sendVerificationCode(normalizedPhone, code);
    return { success: true };
  }

  /** Проверить код и выдать access/refresh токены. При отсутствии номера в БД — создать пользователя. */
  async verifyCode(
    phone: string,
    code: string,
    ip: string,
  ): Promise<{ access_token: string; refresh_token: string }> {
    const normalizedPhone = this.normalizePhone(phone);
    if (!normalizedPhone) {
      throw new BadRequestException('Некорректный номер телефона');
    }
    if (!code || code.length !== 4) {
      throw new BadRequestException('Код должен содержать 4 символа');
    }

    if (this.verificationStore.isVerifyBlocked(normalizedPhone, ip)) {
      const remaining = this.verificationStore.getBlockedRemainingSec(normalizedPhone, ip);
      throw new HttpException(
        `Слишком много попыток. Попробуйте через ${remaining} секунд`,
        HttpStatus.TOO_MANY_REQUESTS,
      );
    }

    const result = this.verificationStore.checkCode(normalizedPhone, ip, code);
    if (!result.ok) {
      if (result.reason === 'expired') {
        throw new BadRequestException('Код истёк. Запросите новый');
      }
      throw new BadRequestException(
        `Неверный код. Осталось ${result.remainingAttempts} попыток`,
      );
    }

    let user = await this.usersService.findByPhone(normalizedPhone);
    if (!user) {
      user = await this.usersService.createByPhone(normalizedPhone);
    }

    return this.issueTokens(user);
  }

  /** Обновить access токен по refresh токену. */
  async refresh(refreshToken: string): Promise<{ access_token: string; refresh_token: string }> {
    const stored = this.refreshTokenStore.get(refreshToken);
    if (!stored) {
      throw new BadRequestException('Недействительный или просроченный refresh token');
    }
    try {
      this.jwtService.verify(refreshToken, { secret: this.configService.get<string>('JWT_SECRET') });
    } catch {
      this.refreshTokenStore.remove(refreshToken);
      throw new BadRequestException('Недействительный refresh token');
    }

    const user = await this.usersService.findById(stored.userId);
    if (!user) {
      this.refreshTokenStore.remove(refreshToken);
      throw new BadRequestException('Пользователь не найден');
    }

    this.refreshTokenStore.remove(refreshToken);
    return this.issueTokens(user);
  }

  private issueTokens(user: User): { access_token: string; refresh_token: string } {
    const payload = {
      sub: user.userId,
      userId: user.userId,
      phone: user.phone,
    };
    const access_token = this.jwtService.sign(payload, {
      expiresIn: jwtConstants.accessTokenExpiresIn,
    });
    const refresh_token = this.jwtService.sign(
      { ...payload, type: 'refresh' },
      { expiresIn: jwtConstants.refreshTokenExpiresIn },
    );
    const decoded = this.jwtService.decode(refresh_token) as { exp: number };
    const expiresAt = decoded?.exp ? decoded.exp * 1000 : Date.now() + 30 * 24 * 60 * 60 * 1000;
    this.refreshTokenStore.set(refresh_token, user.userId, expiresAt);
    return { access_token, refresh_token };
  }

  private normalizePhone(phone: string): string {
    const digits = phone.replace(/\D/g, '');
    if (digits.length < 10) return '';
    if (digits.length === 10 && digits.startsWith('9')) return '+7' + digits;
    if (digits.length === 11 && digits.startsWith('7')) return '+' + digits;
    return digits.startsWith('7') ? '+' + digits : '+' + digits;
  }

  private generateCode(): string {
    return String(Math.floor(1000 + Math.random() * 9000));
  }
}
