import {
  BadGatewayException,
  ForbiddenException,
  Injectable,
  Logger,
  NotFoundException,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios, {
  AxiosInstance,
  AxiosRequestConfig,
  AxiosResponse,
  isAxiosError,
} from 'axios';

/**
 * AtlasProxyService forwards requests to the Atlas server. It presents a scoped PlanFi
 * service key (`X-Planfi-Service-Key: pf_sk_...`), which Atlas validates against the
 * route×method matrix via the Laravel introspection. The key lives ONLY here (server-side) —
 * never in the browser. Uses raw axios (this codebase has no @nestjs/axios).
 */
@Injectable()
export class AtlasProxyService {
  private readonly logger = new Logger(AtlasProxyService.name);
  private readonly http: AxiosInstance;
  private readonly atlasApiUrl: string;
  private readonly atlasApiOrigin: string;
  private readonly serviceKey: string;
  private readonly baseUrlIncludesApiPrefix: boolean;

  constructor(private readonly configService: ConfigService) {
    const baseURL =
      this.configService.get<string>('ATLAS_API_URL') ||
      'http://localhost:3001';
    this.atlasApiUrl = baseURL;
    this.baseUrlIncludesApiPrefix = /\/api\/?$/.test(baseURL);
    this.atlasApiOrigin =
      this.configService.get<string>('ATLAS_API_ORIGIN') ||
      'http://localhost:3001';
    this.serviceKey =
      this.configService.get<string>('BFF_TO_ATLAS_SERVICE_KEY') || '';

    if (!this.serviceKey) {
      this.logger.warn(
        'AtlasProxyService initialized without BFF_TO_ATLAS_SERVICE_KEY — requests to Atlas will fail',
      );
    }

    this.http = axios.create({
      baseURL,
      timeout: 30_000,
      headers: {
        'Content-Type': 'application/json',
      },
    });

    this.http.interceptors.request.use(
      (config) => {
        this.logger.debug(
          `${config.method?.toUpperCase()} ${config.baseURL ?? ''}${config.url ?? ''}`,
        );
        return config;
      },
      (error: unknown) => Promise.reject(error),
    );

    this.http.interceptors.response.use(
      (response) => response,
      (error: unknown) => Promise.reject(error),
    );
  }

  /**
   * Forward a request to Atlas signed with the BFF's scoped service key (X-Planfi-Service-Key).
   */
  async forwardRequest<T>(
    method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH',
    path: string,
    data?: unknown,
    queryParams?: Record<string, unknown>,
  ): Promise<T> {
    if (!this.serviceKey) {
      throw new UnauthorizedException(
        'Atlas proxy not configured: BFF_TO_ATLAS_SERVICE_KEY is missing',
      );
    }

    try {
      const config: AxiosRequestConfig = {
        method,
        url: this.normalizeForwardPath(path),
        headers: {
          'X-Planfi-Service-Key': this.serviceKey,
          Origin: this.atlasApiOrigin,
          'Content-Type': 'application/json',
        },
        params: queryParams,
      };

      if (
        data &&
        (method === 'POST' || method === 'PUT' || method === 'PATCH')
      ) {
        config.data = data;
      }

      const response: AxiosResponse<T> = await this.http.request<T>(config);
      return response.data;
    } catch (err: unknown) {
      this.logger.error(
        `Atlas forward request failed: ${method} ${path}`,
        err instanceof Error ? err.message : String(err),
      );
      throw this.mapUpstreamError(err);
    }
  }

  private normalizeForwardPath(path: string): string {
    const normalizedPath = path.startsWith('/') ? path : `/${path}`;

    if (this.baseUrlIncludesApiPrefix && normalizedPath.startsWith('/api/')) {
      return normalizedPath.slice('/api'.length);
    }

    return normalizedPath;
  }

  private mapUpstreamError(error: unknown): never {
    const hasResponse =
      error && typeof error === 'object' && 'response' in error;

    if (!hasResponse || !(error as any).response) {
      this.logger.error('Atlas network or unknown error');
      throw new BadGatewayException('Atlas upstream error');
    }

    const status = (error as any).response.status;
    const rawData = (error as any).response.data;
    const data =
      typeof rawData === 'object' && rawData !== null && !Array.isArray(rawData)
        ? (rawData as Record<string, unknown>)
        : {};
    const msg =
      typeof data.message === 'string'
        ? data.message
        : (error as any).message || `HTTP ${status}`;

    this.logger.warn(
      `Atlas error: status=${status}, url=${(error as any).config?.url}, method=${(error as any).config?.method}`,
    );

    switch (status) {
      case 401:
        throw new UnauthorizedException(msg);
      case 403:
        throw new ForbiddenException(msg);
      case 404:
        throw new NotFoundException(msg);
      default:
        if (status >= 500) {
          throw new BadGatewayException('Atlas upstream error');
        }
        throw new BadGatewayException(msg);
    }
  }
}
