import axios, { AxiosInstance, AxiosError } from 'axios';
import type {
  Base,
  BaseId,
  Table,
  TableId,
  AirtableRecord,
  RecordId,
  RecordFields,
  Field,
  View,
  ViewId,
  Webhook,
  WebhookId,
  Automation,
  Comment,
  FilterFormula,
  SortConfig,
  ListBasesResponse,
  ListTablesResponse,
  ListRecordsResponse,
  CreateRecordsResponse,
  UpdateRecordsResponse,
  DeleteRecordsResponse,
} from '../types/index.js';

export interface AirtableClientConfig {
  apiKey: string;
  baseUrl?: string;
  metaBaseUrl?: string;
  maxRetries?: number;
  retryDelayMs?: number;
}

export interface ListRecordsOptions {
  fields?: string[];
  filterByFormula?: FilterFormula;
  maxRecords?: number;
  pageSize?: number;
  sort?: SortConfig[];
  view?: string;
  cellFormat?: 'json' | 'string';
  timeZone?: string;
  userLocale?: string;
  offset?: string;
  returnFieldsByFieldId?: boolean;
}

export interface CreateRecordOptions {
  fields: RecordFields;
  typecast?: boolean;
}

export interface UpdateRecordOptions {
  fields: RecordFields;
  typecast?: boolean;
}

export interface DeleteRecordsResult {
  records: Array<{ id: RecordId; deleted: boolean }>;
}

export class AirtableError extends Error {
  constructor(
    message: string,
    public statusCode?: number,
    public response?: unknown
  ) {
    super(message);
    this.name = 'AirtableError';
  }
}

/**
 * Airtable API Client
 *
 * Official API Documentation: https://airtable.com/developers/web/api/introduction
 *
 * Rate Limits:
 * - 5 requests per second per base
 * - Implement exponential backoff for 429 responses
 *
 * Base URLs:
 * - Records API: https://api.airtable.com/v0
 * - Meta API: https://api.airtable.com/v0/meta
 *
 * Pagination:
 * - Records: offset-based (include `offset` from response in next request)
 * - Meta endpoints: cursor-based (vary by endpoint)
 *
 * Batch Limits:
 * - Create/Update: max 10 records per request
 * - Delete: max 10 record IDs per request
 */
export class AirtableClient {
  private client: AxiosInstance;
  private metaClient: AxiosInstance;
  private maxRetries: number;
  private retryDelayMs: number;

  constructor(config: AirtableClientConfig) {
    const baseUrl = config.baseUrl || 'https://api.airtable.com/v0';
    const metaBaseUrl = config.metaBaseUrl || 'https://api.airtable.com/v0/meta';

    this.maxRetries = config.maxRetries || 3;
    this.retryDelayMs = config.retryDelayMs || 1000;

    this.client = axios.create({
      baseURL: baseUrl,
      headers: {
        Authorization: `Bearer ${config.apiKey}`,
        'Content-Type': 'application/json',
      },
      timeout: 30000,
    });

    this.metaClient = axios.create({
      baseURL: metaBaseUrl,
      headers: {
        Authorization: `Bearer ${config.apiKey}`,
        'Content-Type': 'application/json',
      },
      timeout: 30000,
    });

    this.setupInterceptors();
  }

  private setupInterceptors(): void {
    const retryInterceptor = async (error: AxiosError) => {
      const config = error.config as any;
      
      if (!config || !error.response) {
        throw error;
      }

      // Handle rate limiting (429)
      if (error.response.status === 429) {
        const retryCount = config.__retryCount || 0;
        
        if (retryCount < this.maxRetries) {
          config.__retryCount = retryCount + 1;
          const delay = this.retryDelayMs * Math.pow(2, retryCount);
          
          await new Promise((resolve) => setTimeout(resolve, delay));
          return this.client.request(config);
        }
      }

      throw this.handleError(error);
    };

    this.client.interceptors.response.use(
      (response) => response,
      retryInterceptor
    );

    this.metaClient.interceptors.response.use(
      (response) => response,
      retryInterceptor
    );
  }

  private handleError(error: AxiosError): AirtableError {
    if (error.response) {
      const data = error.response.data as any;
      const message = data?.error?.message || data?.message || 'Airtable API error';
      return new AirtableError(message, error.response.status, data);
    }
    return new AirtableError(error.message);
  }

  // ============================================================================
  // Bases API
  // ============================================================================

  async listBases(offset?: string): Promise<ListBasesResponse> {
    const params: Record<string, string> = {};
    if (offset) params.offset = offset;

    const response = await this.metaClient.get<ListBasesResponse>('/bases', { params });
    return response.data;
  }

  async getBase(baseId: BaseId): Promise<Base> {
    const response = await this.metaClient.get<{ id: string; name: string; permissionLevel: string }>(
      `/bases/${baseId}`
    );
    return response.data as Base;
  }

  // ============================================================================
  // Tables API (Meta)
  // ============================================================================

  async listTables(baseId: BaseId): Promise<ListTablesResponse> {
    const response = await this.metaClient.get<ListTablesResponse>(`/bases/${baseId}/tables`);
    return response.data;
  }

  async getTable(baseId: BaseId, tableId: TableId): Promise<Table> {
    const response = await this.metaClient.get<Table>(`/bases/${baseId}/tables/${tableId}`);
    return response.data;
  }

  // ============================================================================
  // Fields API (Meta)
  // ============================================================================

  async listFields(baseId: BaseId, tableId: TableId): Promise<Field[]> {
    const table = await this.getTable(baseId, tableId);
    return table.fields;
  }

  async getField(baseId: BaseId, tableId: TableId, fieldId: string): Promise<Field> {
    const fields = await this.listFields(baseId, tableId);
    const field = fields.find((f) => f.id === fieldId);
    if (!field) {
      throw new AirtableError(`Field ${fieldId} not found in table ${tableId}`);
    }
    return field;
  }

  // ============================================================================
  // Views API (Meta)
  // ============================================================================

  async listViews(baseId: BaseId, tableId: TableId): Promise<View[]> {
    const table = await this.getTable(baseId, tableId);
    return table.views;
  }

  async getView(baseId: BaseId, tableId: TableId, viewId: ViewId): Promise<View> {
    const views = await this.listViews(baseId, tableId);
    const view = views.find((v) => v.id === viewId);
    if (!view) {
      throw new AirtableError(`View ${viewId} not found in table ${tableId}`);
    }
    return view;
  }

  // ============================================================================
  // Records API
  // ============================================================================

  async listRecords(
    baseId: BaseId,
    tableIdOrName: string,
    options?: ListRecordsOptions
  ): Promise<ListRecordsResponse> {
    const params: Record<string, unknown> = {};

    if (options?.fields) params.fields = options.fields;
    if (options?.filterByFormula) params.filterByFormula = options.filterByFormula;
    if (options?.maxRecords) params.maxRecords = options.maxRecords;
    if (options?.pageSize) params.pageSize = options.pageSize;
    if (options?.view) params.view = options.view;
    if (options?.cellFormat) params.cellFormat = options.cellFormat;
    if (options?.timeZone) params.timeZone = options.timeZone;
    if (options?.userLocale) params.userLocale = options.userLocale;
    if (options?.offset) params.offset = options.offset;
    if (options?.returnFieldsByFieldId) params.returnFieldsByFieldId = options.returnFieldsByFieldId;

    if (options?.sort) {
      options.sort.forEach((sortItem, index) => {
        params[`sort[${index}][field]`] = sortItem.field;
        params[`sort[${index}][direction]`] = sortItem.direction;
      });
    }

    const response = await this.client.get<ListRecordsResponse>(`/${baseId}/${encodeURIComponent(tableIdOrName)}`, {
      params,
    });
    return response.data;
  }

  async getRecord(baseId: BaseId, tableIdOrName: string, recordId: RecordId): Promise<AirtableRecord> {
    const response = await this.client.get<AirtableRecord>(
      `/${baseId}/${encodeURIComponent(tableIdOrName)}/${recordId}`
    );
    return response.data;
  }

  /**
   * Create records (max 10 per request)
   */
  async createRecords(
    baseId: BaseId,
    tableIdOrName: string,
    records: CreateRecordOptions[],
    typecast?: boolean
  ): Promise<CreateRecordsResponse> {
    if (records.length > 10) {
      throw new AirtableError('Cannot create more than 10 records per request');
    }

    const payload: Record<string, unknown> = {
      records: records.map((r) => ({ fields: r.fields })),
    };

    if (typecast !== undefined) {
      payload.typecast = typecast;
    }

    const response = await this.client.post<CreateRecordsResponse>(
      `/${baseId}/${encodeURIComponent(tableIdOrName)}`,
      payload
    );
    return response.data;
  }

  /**
   * Update records (max 10 per request)
   */
  async updateRecords(
    baseId: BaseId,
    tableIdOrName: string,
    records: Array<{ id: RecordId } & UpdateRecordOptions>,
    typecast?: boolean
  ): Promise<UpdateRecordsResponse> {
    if (records.length > 10) {
      throw new AirtableError('Cannot update more than 10 records per request');
    }

    const payload: Record<string, unknown> = {
      records: records.map((r) => ({ id: r.id, fields: r.fields })),
    };

    if (typecast !== undefined) {
      payload.typecast = typecast;
    }

    const response = await this.client.patch<UpdateRecordsResponse>(
      `/${baseId}/${encodeURIComponent(tableIdOrName)}`,
      payload
    );
    return response.data;
  }

  /**
   * Delete records (max 10 per request)
   */
  async deleteRecords(
    baseId: BaseId,
    tableIdOrName: string,
    recordIds: RecordId[]
  ): Promise<DeleteRecordsResult> {
    if (recordIds.length > 10) {
      throw new AirtableError('Cannot delete more than 10 records per request');
    }

    const params = recordIds.map((id) => `records[]=${id}`).join('&');
    const response = await this.client.delete<DeleteRecordsResult>(
      `/${baseId}/${encodeURIComponent(tableIdOrName)}?${params}`
    );
    return response.data;
  }

  // ============================================================================
  // Webhooks API
  // ============================================================================

  async listWebhooks(baseId: BaseId): Promise<Webhook[]> {
    const response = await this.client.get<{ webhooks: Webhook[] }>(`/${baseId}/webhooks`);
    return response.data.webhooks;
  }

  async createWebhook(baseId: BaseId, notificationUrl: string, specification: unknown): Promise<Webhook> {
    const response = await this.client.post<Webhook>(`/${baseId}/webhooks`, {
      notificationUrl,
      specification,
    });
    return response.data;
  }

  async deleteWebhook(baseId: BaseId, webhookId: WebhookId): Promise<void> {
    await this.client.delete(`/${baseId}/webhooks/${webhookId}`);
  }

  async refreshWebhook(baseId: BaseId, webhookId: WebhookId): Promise<Webhook> {
    const response = await this.client.post<Webhook>(`/${baseId}/webhooks/${webhookId}/refresh`);
    return response.data;
  }

  // ============================================================================
  // Comments API
  // ============================================================================

  async listComments(baseId: BaseId, tableIdOrName: string, recordId: RecordId): Promise<Comment[]> {
    const response = await this.client.get<{ comments: Comment[] }>(
      `/${baseId}/${encodeURIComponent(tableIdOrName)}/${recordId}/comments`
    );
    return response.data.comments;
  }

  async createComment(baseId: BaseId, tableIdOrName: string, recordId: RecordId, text: string): Promise<Comment> {
    const response = await this.client.post<Comment>(
      `/${baseId}/${encodeURIComponent(tableIdOrName)}/${recordId}/comments`,
      { text }
    );
    return response.data;
  }

  async updateComment(
    baseId: BaseId,
    tableIdOrName: string,
    recordId: RecordId,
    commentId: string,
    text: string
  ): Promise<Comment> {
    const response = await this.client.patch<Comment>(
      `/${baseId}/${encodeURIComponent(tableIdOrName)}/${recordId}/comments/${commentId}`,
      { text }
    );
    return response.data;
  }

  async deleteComment(
    baseId: BaseId,
    tableIdOrName: string,
    recordId: RecordId,
    commentId: string
  ): Promise<void> {
    await this.client.delete(`/${baseId}/${encodeURIComponent(tableIdOrName)}/${recordId}/comments/${commentId}`);
  }

  // ============================================================================
  // Automations API (Read-only for now)
  // ============================================================================

  /**
   * Note: Airtable's API does not currently provide direct automation management.
   * This is a placeholder for future functionality or webhook-based automation triggers.
   */
  async listAutomations(baseId: BaseId): Promise<Automation[]> {
    throw new AirtableError('Automations API not yet supported by Airtable');
  }
}
