import { Injectable, Inject } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { JwtService } from "@nestjs/jwt";
import { createHash } from "crypto";
import { Redis } from "ioredis";
import { Logger } from "nestjs-pino";
import { parseJwtExpiry } from "../../utils/jwt-expiry.util";

@Injectable()
export class TokenBlacklistService {
  constructor(
    @Inject("REDIS_CLIENT") private readonly redis: Redis,
    private readonly configService: ConfigService,
    private readonly jwtService: JwtService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async blacklistToken(token: string, _expiresIn?: number): Promise<void> {
    // Validate token format first
    if (!token || typeof token !== "string" || token.split(".").length !== 3) {
      this.logger.warn("Invalid token format provided for blacklisting");
      return;
    }

    // Use JwtService to safely decode and validate token
    try {
      const decoded: unknown = this.jwtService.decode(token);

      if (!decoded || typeof decoded !== "object" || Array.isArray(decoded)) {
        this.logger.warn("Invalid JWT payload structure for blacklisting");
        return;
      }

      const { sub, exp } = decoded as { sub?: unknown; exp?: unknown };
      if (typeof sub !== "string" || typeof exp !== "number") {
        this.logger.warn("Invalid JWT payload structure for blacklisting");
        return;
      }

      const expiryTime = exp * 1000; // Convert to milliseconds
      const currentTime = Date.now();
      const ttl = Math.max(0, Math.floor((expiryTime - currentTime) / 1000)); // Convert to seconds

      if (ttl > 0) {
        await this.redis.setex(this.buildBlacklistKey(token), ttl, "1");
        this.logger.debug(`Token blacklisted successfully for ${ttl} seconds`);
      } else {
        this.logger.debug("Token already expired, not blacklisting");
      }
    } catch {
      // If we can't parse the token, blacklist it for the default JWT expiry time
      try {
        const defaultTtl = parseJwtExpiry(this.configService.get("JWT_EXPIRES_IN", "7d"));
        await this.redis.setex(this.buildBlacklistKey(token), defaultTtl, "1");
        this.logger.debug(`Token blacklisted with default TTL: ${defaultTtl} seconds`);
      } catch (err) {
        this.logger.warn(
          "Failed to write token to Redis blacklist; proceeding without persistence",
          {
            error: err instanceof Error ? err.message : String(err),
          }
        );
      }
    }
  }

  async isTokenBlacklisted(token: string): Promise<boolean> {
    try {
      const result = await this.redis.get(this.buildBlacklistKey(token));
      return result !== null;
    } catch (err) {
      // If Redis is unavailable, treat as not blacklisted to avoid blocking auth
      this.logger.warn("Redis unavailable during blacklist check; allowing request", {
        error: err instanceof Error ? err.message : String(err),
      });
      return false;
    }
  }

  private buildBlacklistKey(token: string): string {
    return `blacklist:${this.hashToken(token)}`;
  }

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