import fetch, { Response } from 'node-fetch';
import { AuthManager } from '../auth/oauth.js';
import { RateLimiter } from './rate-limiter.js';
import type { MetaApiError as MetaApiErrorType, MetaPaginatedResponse } from '../types/meta-api.js';

export interface MetaClientConfig {
  apiVersion?: string;
  baseUrl?: string;
}

export class MetaApiClientError extends Error {
  constructor(
    message: string,
    public code: number,
    public type: string,
    public subcode?: number,
    public fbtraceId?: string
  ) {
    super(message);
    this.name = 'MetaApiClientError';
  }
}

export class MetaApiClient {
  private authManager: AuthManager;
  private rateLimiter: RateLimiter;
  private apiVersion: string;
  private baseUrl: string;

  constructor(
    authManager: AuthManager,
    config: MetaClientConfig = {},
    rateLimiter?: RateLimiter
  ) {
    this.authManager = authManager;
    this.rateLimiter = rateLimiter || new RateLimiter();
    this.apiVersion = config.apiVersion || 'v21.0';
    this.baseUrl = config.baseUrl || 'https://graph.facebook.com';
  }

  /**
   * Build full URL for API endpoint
   */
  private buildUrl(path: string): string {
    // Remove leading slash if present
    const cleanPath = path.startsWith('/') ? path.slice(1) : path;
    return `${this.baseUrl}/${this.apiVersion}/${cleanPath}`;
  }

  /**
   * Add authentication and app secret proof to params
   */
  private addAuthParams(params: Record<string, unknown>): Record<string, unknown> {
    const authParams: Record<string, unknown> = {
      ...params,
      access_token: this.authManager.getAccessToken(),
    };

    const appSecretProof = this.authManager.generateAppSecretProof();
    if (appSecretProof) {
      authParams.appsecret_proof = appSecretProof;
    }

    return authParams;
  }

  /**
   * Convert params object to URLSearchParams
   */
  private toSearchParams(params: Record<string, unknown>): URLSearchParams {
    const searchParams = new URLSearchParams();
    
    for (const [key, value] of Object.entries(params)) {
      if (value === undefined || value === null) {
        continue;
      }
      
      if (Array.isArray(value)) {
        searchParams.set(key, JSON.stringify(value));
      } else if (typeof value === 'object') {
        searchParams.set(key, JSON.stringify(value));
      } else {
        searchParams.set(key, String(value));
      }
    }
    
    return searchParams;
  }

  /**
   * Parse and throw Meta API errors
   */
  private async handleErrorResponse(response: Response): Promise<never> {
    let errorData: MetaApiErrorType | undefined;
    
    try {
      errorData = await response.json() as MetaApiErrorType;
    } catch {
      throw new MetaApiClientError(
        `HTTP ${response.status}: ${response.statusText}`,
        response.status,
        'HTTP_ERROR'
      );
    }

    if (errorData?.error) {
      throw new MetaApiClientError(
        errorData.error.message,
        errorData.error.code,
        errorData.error.type,
        errorData.error.error_subcode,
        errorData.error.fbtrace_id
      );
    }

    throw new MetaApiClientError(
      `Unknown error: ${response.statusText}`,
      response.status,
      'UNKNOWN'
    );
  }

  /**
   * Make a GET request
   */
  async get<T>(path: string, params: Record<string, unknown> = {}): Promise<T> {
    return this.rateLimiter.enqueue(async () => {
      const authParams = this.addAuthParams(params);
      const searchParams = this.toSearchParams(authParams);
      const url = `${this.buildUrl(path)}?${searchParams.toString()}`;

      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Accept': 'application/json',
        },
      });

      // Parse rate limit headers
      this.rateLimiter.parseUsageHeaders(response.headers as unknown as Headers);

      if (!response.ok) {
        await this.handleErrorResponse(response);
      }

      return await response.json() as T;
    });
  }

  /**
   * Make a POST request
   */
  async post<T>(path: string, data: Record<string, unknown> = {}): Promise<T> {
    return this.rateLimiter.enqueue(async () => {
      const authParams = this.addAuthParams(data);
      const searchParams = this.toSearchParams(authParams);
      const url = this.buildUrl(path);

      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Accept': 'application/json',
        },
        body: searchParams.toString(),
      });

      // Parse rate limit headers
      this.rateLimiter.parseUsageHeaders(response.headers as unknown as Headers);

      if (!response.ok) {
        await this.handleErrorResponse(response);
      }

      return await response.json() as T;
    });
  }

  /**
   * Make a DELETE request
   */
  async delete<T>(path: string, params: Record<string, unknown> = {}): Promise<T> {
    return this.rateLimiter.enqueue(async () => {
      const authParams = this.addAuthParams(params);
      const searchParams = this.toSearchParams(authParams);
      const url = `${this.buildUrl(path)}?${searchParams.toString()}`;

      const response = await fetch(url, {
        method: 'DELETE',
        headers: {
          'Accept': 'application/json',
        },
      });

      // Parse rate limit headers
      this.rateLimiter.parseUsageHeaders(response.headers as unknown as Headers);

      if (!response.ok) {
        await this.handleErrorResponse(response);
      }

      return await response.json() as T;
    });
  }

  /**
   * Fetch all pages of a paginated response
   */
  async getAllPages<T>(
    path: string,
    params: Record<string, unknown> = {},
    maxPages: number = 10
  ): Promise<T[]> {
    const results: T[] = [];
    let currentPage = 0;
    let nextUrl: string | undefined;

    // Ensure we request enough results per page
    const paginatedParams = {
      ...params,
      limit: params.limit || 100,
    };

    // Get first page
    const firstPage = await this.get<MetaPaginatedResponse<T>>(path, paginatedParams);
    results.push(...firstPage.data);
    nextUrl = firstPage.paging?.next;
    currentPage++;

    // Follow pagination cursors
    while (nextUrl && currentPage < maxPages) {
      const response = await this.rateLimiter.enqueue(async () => {
        const res = await fetch(nextUrl!, {
          headers: {
            'Accept': 'application/json',
          },
        });

        // Parse rate limit headers
        this.rateLimiter.parseUsageHeaders(res.headers as unknown as Headers);

        if (!res.ok) {
          await this.handleErrorResponse(res);
        }

        return await res.json() as MetaPaginatedResponse<T>;
      });

      results.push(...response.data);
      nextUrl = response.paging?.next;
      currentPage++;
    }

    return results;
  }

  /**
   * Upload media (image or video)
   */
  async uploadMedia(
    adAccountId: string,
    fileBuffer: Buffer,
    filename: string,
    type: 'image' | 'video'
  ): Promise<{ hash?: string; id?: string }> {
    const FormData = (await import('form-data')).default;
    const form = new FormData();
    
    form.append('access_token', this.authManager.getAccessToken());
    
    const appSecretProof = this.authManager.generateAppSecretProof();
    if (appSecretProof) {
      form.append('appsecret_proof', appSecretProof);
    }

    form.append('source', fileBuffer, { filename });

    const endpoint = type === 'image' ? 'adimages' : 'advideos';
    const url = this.buildUrl(`${adAccountId}/${endpoint}`);

    return this.rateLimiter.enqueue(async () => {
      const response = await fetch(url, {
        method: 'POST',
        body: form as never,
      });

      // Parse rate limit headers
      this.rateLimiter.parseUsageHeaders(response.headers as unknown as Headers);

      if (!response.ok) {
        await this.handleErrorResponse(response);
      }

      const result = await response.json() as Record<string, unknown>;
      
      if (type === 'image') {
        const images = result.images as Record<string, { hash: string }>;
        return { hash: images[filename]?.hash };
      } else {
        return { id: result.id as string };
      }
    });
  }

  /**
   * Get the rate limiter instance
   */
  getRateLimiter(): RateLimiter {
    return this.rateLimiter;
  }

  /**
   * Get current API version
   */
  getApiVersion(): string {
    return this.apiVersion;
  }
}
