import { HttpService } from '@nestjs/axios';
import { Injectable, ServiceUnavailableException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { firstValueFrom } from 'rxjs';

type TokenResponse = {
  access_token: string;
  refresh_token: string;
  expires_in?: number;
};

type TokenSet = {
  accessToken: string;
  refreshToken: string;
  expiresAt?: number;
};

@Injectable()
export class VllmAuthService {
  private tokenSet: TokenSet | null;
  private refreshPromise: Promise<string> | null = null;

  constructor(
    private readonly http: HttpService,
    private readonly config: ConfigService,
  ) {
    const accessToken = this.config.get<string>('VLLM_ACCESS_TOKEN');
    const refreshToken = this.config.get<string>('VLLM_REFRESH_TOKEN');
    this.tokenSet =
      accessToken && refreshToken ? { accessToken, refreshToken } : null;
  }

  async getAccessToken() {
    if (this.tokenSet?.accessToken && !this.isExpiringSoon()) {
      return this.tokenSet.accessToken;
    }
    return this.issueToken();
  }

  async refreshToken() {
    if (!this.refreshPromise) {
      this.refreshPromise = this.refreshTokenInternal().finally(() => {
        this.refreshPromise = null;
      });
    }

    return this.refreshPromise;
  }

  private async refreshTokenInternal() {
    if (!this.tokenSet?.refreshToken) return this.issueToken();

    try {
      const response = await firstValueFrom(
        this.http.post<TokenResponse>(
          `${this.baseUrl}/api/external/llm/refresh`,
          { refresh_token: this.tokenSet.refreshToken },
          { timeout: this.timeoutMs },
        ),
      );
      this.setToken(response.data);
      return this.tokenSet!.accessToken;
    } catch {
      return this.issueToken();
    }
  }

  private async issueToken() {
    const response = await firstValueFrom(
      this.http.post<TokenResponse>(
        `${this.baseUrl}/api/external/llm/token`,
        {
          api_key: this.config.getOrThrow<string>('VLLM_API_KEY'),
          secret_key: this.config.getOrThrow<string>('VLLM_SECRET_KEY'),
        },
        { timeout: this.timeoutMs },
      ),
    );
    this.setToken(response.data);
    return this.tokenSet!.accessToken;
  }

  private setToken(response: TokenResponse) {
    this.tokenSet = {
      accessToken: response.access_token,
      refreshToken: response.refresh_token,
      expiresAt: response.expires_in
        ? Date.now() + response.expires_in * 1000
        : undefined,
    };
  }

  private isExpiringSoon() {
    if (!this.tokenSet?.expiresAt) return false;
    return this.tokenSet.expiresAt - Date.now() < 60_000;
  }

  private get baseUrl() {
    const value = this.config.get<string>('VLLM_BASE_URL')?.trim();
    if (!value) {
      throw new ServiceUnavailableException('VLLM_BASE_URL is not configured');
    }
    return value.replace(/\/$/, '');
  }

  private get timeoutMs() {
    return Number(this.config.get<string>('VLLM_TIMEOUT_MS') ?? 20000);
  }
}
