import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Inject } from '@nestjs/common';
import { REFRESH_TOKEN_REPOSITORY, IRefreshTokenRepository } from '../../domain/repositories/refresh-token.repository.interface';
import * as crypto from 'crypto';

export interface JwtPayload {
  sub: string;
  role: string;
  kycLevel: number;
  type: 'access' | 'refresh';
}

@Injectable()
export class TokenService {
  private readonly accessSecret: string;
  private readonly refreshSecret: string;
  private readonly accessExpiry: string;
  private readonly refreshExpiry: string;

  constructor(
    private readonly jwtService: JwtService,
    @Inject(REFRESH_TOKEN_REPOSITORY) private readonly refreshTokenRepo: IRefreshTokenRepository,
  ) {
    this.accessSecret = process.env.JWT_ACCESS_SECRET || 'dev-access-secret';
    this.refreshSecret = process.env.JWT_REFRESH_SECRET || 'dev-refresh-secret';
    this.accessExpiry = process.env.JWT_ACCESS_EXPIRY || '15m';
    this.refreshExpiry = process.env.JWT_REFRESH_EXPIRY || '7d';
  }

  async generateTokenPair(
    userId: string,
    role: string,
    kycLevel: number,
  ): Promise<{ accessToken: string; refreshToken: string; expiresIn: number }> {
    const accessPayload: JwtPayload = {
      sub: userId,
      role,
      kycLevel,
      type: 'access',
    };

    const refreshPayload: JwtPayload = {
      sub: userId,
      role,
      kycLevel,
      type: 'refresh',
    };

    const accessToken = this.jwtService.sign(accessPayload, {
      secret: this.accessSecret,
      expiresIn: this.accessExpiry,
    });

    const refreshToken = this.jwtService.sign(refreshPayload, {
      secret: this.refreshSecret,
      expiresIn: this.refreshExpiry,
    });

    return {
      accessToken,
      refreshToken,
      expiresIn: this.parseExpiryToSeconds(this.accessExpiry),
    };
  }

  async verifyAccessToken(token: string): Promise<JwtPayload> {
    try {
      const payload = this.jwtService.verify<JwtPayload>(token, {
        secret: this.accessSecret,
      });
      if (payload.type !== 'access') {
        throw new UnauthorizedException('Invalid token type');
      }
      return payload;
    } catch (error) {
      throw new UnauthorizedException('Invalid or expired access token');
    }
  }

  async verifyRefreshToken(token: string): Promise<JwtPayload> {
    try {
      const payload = this.jwtService.verify<JwtPayload>(token, {
        secret: this.refreshSecret,
      });
      if (payload.type !== 'refresh') {
        throw new UnauthorizedException('Invalid token type');
      }

      // Check if token is revoked
      const hash = this.hashToken(token);
      const stored = await this.refreshTokenRepo.findByTokenHash(hash);
      if (!stored || stored.isRevoked) {
        throw new UnauthorizedException('Refresh token has been revoked');
      }

      return payload;
    } catch (error) {
      if (error instanceof UnauthorizedException) throw error;
      throw new UnauthorizedException('Invalid or expired refresh token');
    }
  }

  async storeRefreshToken(
    userId: string,
    token: string,
    deviceInfo?: string,
    ipAddress?: string,
  ): Promise<void> {
    const hash = this.hashToken(token);
    const expiresAt = new Date();
    expiresAt.setDate(expiresAt.getDate() + 7); // 7 days

    await this.refreshTokenRepo.create({
      userId,
      tokenHash: hash,
      deviceInfo: deviceInfo || null,
      ipAddress: ipAddress || null,
      isRevoked: false,
      expiresAt,
    });
  }

  async revokeRefreshToken(token: string): Promise<void> {
    const hash = this.hashToken(token);
    const stored = await this.refreshTokenRepo.findByTokenHash(hash);
    if (stored) {
      await this.refreshTokenRepo.revokeToken(stored.id);
    }
  }

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

  private parseExpiryToSeconds(expiry: string): number {
    const match = expiry.match(/^(\d+)(s|m|h|d)$/);
    if (!match) return 900; // default 15m
    const value = parseInt(match[1], 10);
    switch (match[2]) {
      case 's': return value;
      case 'm': return value * 60;
      case 'h': return value * 3600;
      case 'd': return value * 86400;
      default: return 900;
    }
  }
}
