import { Injectable, Inject, ServiceUnavailableException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Redis } from "ioredis";
import { Logger } from "nestjs-pino";
import type { AuthTokens } from "@customer-portal/domain/auth";
import type { UserAuth, UserRole } from "@customer-portal/domain/customer";
import type { DeviceInfo } from "./token.types.js";
import { TokenGeneratorService } from "./token-generator.service.js";
import { TokenRefreshService } from "./token-refresh.service.js";
import { TokenRevocationService } from "./token-revocation.service.js";

const ERROR_SERVICE_UNAVAILABLE = "Authentication service temporarily unavailable";

/**
 * Auth Token Service
 *
 * Thin orchestrator that delegates to focused services:
 * - TokenGeneratorService: token creation
 * - TokenRefreshService: refresh + rotation logic
 * - TokenRevocationService: token revocation
 *
 * Preserves the existing public API so consumers don't need changes.
 */
@Injectable()
export class AuthTokenService {
  private readonly requireRedisForTokens: boolean;
  private readonly maintenanceMode: boolean;
  private readonly maintenanceMessage: string;

  constructor(
    private readonly generator: TokenGeneratorService,
    private readonly refreshService: TokenRefreshService,
    private readonly revocation: TokenRevocationService,
    configService: ConfigService,
    @Inject("REDIS_CLIENT") private readonly redis: Redis,
    @Inject(Logger) private readonly logger: Logger
  ) {
    this.requireRedisForTokens =
      configService.get("AUTH_REQUIRE_REDIS_FOR_TOKENS", "false") === "true";
    this.maintenanceMode = configService.get("AUTH_MAINTENANCE_MODE", "false") === "true";
    this.maintenanceMessage = configService.get(
      "AUTH_MAINTENANCE_MESSAGE",
      "Authentication service is temporarily unavailable for maintenance. Please try again later."
    );
  }

  private checkServiceAvailability(): void {
    if (this.maintenanceMode) {
      this.logger.warn("Authentication service in maintenance mode", {
        maintenanceMessage: this.maintenanceMessage,
      });
      throw new ServiceUnavailableException(this.maintenanceMessage);
    }

    if (this.requireRedisForTokens && this.redis.status !== "ready") {
      this.logger.error("Redis required for token operations but not available", {
        redisStatus: this.redis.status,
        requireRedisForTokens: this.requireRedisForTokens,
      });
      throw new ServiceUnavailableException(ERROR_SERVICE_UNAVAILABLE);
    }
  }

  /**
   * Generate a new token pair with refresh token rotation
   */
  async generateTokenPair(
    user: { id: string; email: string; role?: UserRole },
    deviceInfo?: DeviceInfo
  ): Promise<AuthTokens> {
    this.checkServiceAvailability();
    return this.generator.generateTokenPair(user, deviceInfo);
  }

  /**
   * Refresh access token using refresh token rotation
   */
  async refreshTokens(
    refreshToken: string,
    deviceInfo?: DeviceInfo
  ): Promise<{ tokens: AuthTokens; user: UserAuth }> {
    this.checkServiceAvailability();
    return this.refreshService.refreshTokens(refreshToken, deviceInfo);
  }

  /**
   * Revoke all refresh tokens for a user.
   * Intentionally skips `checkServiceAvailability()` — logout must succeed
   * even during maintenance windows so users are never locked into a session.
   */
  async revokeAllUserTokens(userId: string): Promise<void> {
    return this.revocation.revokeAllUserTokens(userId);
  }
}
