import { randomBytes, createHash } from 'crypto';
import { Inject, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import {
  REFRESH_TOKEN_REPOSITORY,
  type IRefreshTokenRepository,
} from '../../domain/repositories/refresh-token.repository';

export interface JwtPayload {
  sub: string;
  phone: string;
  role: string;
}

export interface TokenPair {
  accessToken: string;
  refreshToken: string;
  expiresIn: number;
}

export interface RotateResult {
  userId: string;
  refreshToken: string;
}

@Injectable()
export class TokenService {
  private readonly REFRESH_TOKEN_EXPIRY_DAYS = 30;

  constructor(
    private readonly jwtService: JwtService,
    @Inject(REFRESH_TOKEN_REPOSITORY)
    private readonly refreshTokenRepo: IRefreshTokenRepository,
  ) {}

  async generateTokenPair(payload: JwtPayload): Promise<TokenPair> {
    const accessToken = this.jwtService.sign(payload);

    const rawRefreshToken = randomBytes(64).toString('hex');
    const hashedToken = this.hashToken(rawRefreshToken);
    const family = randomBytes(16).toString('hex');

    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + this.REFRESH_TOKEN_EXPIRY_DAYS);

    await this.refreshTokenRepo.create({
      userId: payload.sub,
      token: hashedToken,
      family,
      expiresAt,
      revokedAt: null,
    });

    return {
      accessToken,
      refreshToken: `${family}.${rawRefreshToken}`,
      expiresIn: 900,
    };
  }

  async rotateRefreshToken(refreshToken: string): Promise<RotateResult | null> {
    const dotIndex = refreshToken.indexOf('.');
    if (dotIndex === -1) return null;

    const family = refreshToken.substring(0, dotIndex);
    const rawToken = refreshToken.substring(dotIndex + 1);
    if (!family || !rawToken) return null;

    const hashedToken = this.hashToken(rawToken);
    const existing = await this.refreshTokenRepo.findByToken(hashedToken);

    if (!existing) {
      // Possible token reuse attack — revoke entire family
      await this.refreshTokenRepo.revokeByFamily(family);
      return null;
    }

    if (existing.revokedAt || existing.expiresAt < new Date()) {
      await this.refreshTokenRepo.revokeByFamily(existing.family);
      return null;
    }

    // Revoke all tokens in this family
    await this.refreshTokenRepo.revokeByFamily(existing.family);

    // Create new token in a new family
    const newRawToken = randomBytes(64).toString('hex');
    const newHashedToken = this.hashToken(newRawToken);
    const newFamily = randomBytes(16).toString('hex');

    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + this.REFRESH_TOKEN_EXPIRY_DAYS);

    await this.refreshTokenRepo.create({
      userId: existing.userId,
      token: newHashedToken,
      family: newFamily,
      expiresAt,
      revokedAt: null,
    });

    return {
      userId: existing.userId,
      refreshToken: `${newFamily}.${newRawToken}`,
    };
  }

  generateAccessToken(payload: JwtPayload): string {
    return this.jwtService.sign(payload);
  }

  async revokeAllUserTokens(userId: string): Promise<void> {
    await this.refreshTokenRepo.revokeAllForUser(userId);
  }

  verifyAccessToken(token: string): JwtPayload | null {
    try {
      return this.jwtService.verify<JwtPayload>(token);
    } catch {
      return null;
    }
  }

  private hashToken(token: string): string {
    return createHash('sha256').update(token).digest('hex');
  }
}
