import axios, { AxiosInstance, AxiosError } from 'axios';
import type {
  AcuityCredentials,
  Appointment,
  AppointmentType,
  AvailabilityDate,
  Client,
  Calendar,
  Product,
  Form,
  FormField,
  Label,
  Webhook,
  Coupon,
  Block,
  PaginatedResponse,
  ErrorResponse
} from '../types/index.js';

export class AcuityClient {
  private client: AxiosInstance;
  private credentials: AcuityCredentials;

  constructor(credentials: AcuityCredentials) {
    this.credentials = credentials;

    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    };

    if (credentials.oauth2Token) {
      headers['Authorization'] = `Bearer ${credentials.oauth2Token}`;
    }

    this.client = axios.create({
      baseURL: 'https://acuityscheduling.com/api/v1',
      headers,
      ...(credentials.oauth2Token ? {} : {
        auth: {
          username: credentials.userId,
          password: credentials.apiKey
        }
      })
    });

    // Error interceptor
    this.client.interceptors.response.use(
      response => response,
      (error: AxiosError<ErrorResponse>) => {
        if (error.response?.data) {
          const err = new Error(error.response.data.message || error.response.data.error);
          (err as any).statusCode = error.response.status;
          throw err;
        }
        throw error;
      }
    );
  }

  // Generic paginated GET request
  private async paginatedGet<T>(
    endpoint: string,
    params: Record<string, any> = {}
  ): Promise<T[]> {
    const allResults: T[] = [];
    let page = 0;
    const limit = params.limit || 100;

    while (true) {
      const response = await this.client.get<T[]>(endpoint, {
        params: { ...params, limit, offset: page * limit }
      });

      allResults.push(...response.data);

      if (response.data.length < limit) {
        break;
      }
      page++;
    }

    return allResults;
  }

  // Appointments
  async listAppointments(params?: {
    minDate?: string;
    maxDate?: string;
    calendarID?: number;
    canceled?: boolean;
  }): Promise<Appointment[]> {
    return this.paginatedGet<Appointment>('/appointments', params);
  }

  async getAppointment(id: number): Promise<Appointment> {
    const response = await this.client.get<Appointment>(`/appointments/${id}`);
    return response.data;
  }

  async createAppointment(data: {
    appointmentTypeID: number;
    datetime: string;
    firstName: string;
    lastName: string;
    email: string;
    phone?: string;
    fields?: Array<{ id: number; value: string }>;
    calendarID?: number;
    certificate?: string;
  }): Promise<Appointment> {
    const response = await this.client.post<Appointment>('/appointments', data);
    return response.data;
  }

  async updateAppointment(id: number, data: Partial<Appointment>): Promise<Appointment> {
    const response = await this.client.put<Appointment>(`/appointments/${id}`, data);
    return response.data;
  }

  async cancelAppointment(id: number, params?: { noEmail?: boolean }): Promise<Appointment> {
    const response = await this.client.put<Appointment>(`/appointments/${id}/cancel`, null, { params });
    return response.data;
  }

  async rescheduleAppointment(id: number, datetime: string): Promise<Appointment> {
    const response = await this.client.put<Appointment>(`/appointments/${id}/reschedule`, { datetime });
    return response.data;
  }

  async listAppointmentTypes(): Promise<AppointmentType[]> {
    const response = await this.client.get<AppointmentType[]>('/appointment-types');
    return response.data;
  }

  async getAppointmentType(id: number): Promise<AppointmentType> {
    const response = await this.client.get<AppointmentType>(`/appointment-types/${id}`);
    return response.data;
  }

  // Availability
  async getAvailabilityDates(params: {
    appointmentTypeID: number;
    month?: string;
    calendarID?: number;
    timezone?: string;
  }): Promise<AvailabilityDate[]> {
    const response = await this.client.get<AvailabilityDate[]>('/availability/dates', { params });
    return response.data;
  }

  async getAvailabilityTimes(params: {
    appointmentTypeID: number;
    date: string;
    calendarID?: number;
    timezone?: string;
  }): Promise<{ time: string }[]> {
    const response = await this.client.get<{ time: string }[]>('/availability/times', { params });
    return response.data;
  }

  async getAvailabilityClasses(params?: {
    appointmentTypeID?: number;
    minDate?: string;
    maxDate?: string;
  }): Promise<any[]> {
    const response = await this.client.get<any[]>('/availability/classes', { params });
    return response.data;
  }

  async checkAvailability(params: {
    appointmentTypeID: number;
    date: string;
    time: string;
    calendarID?: number;
  }): Promise<{ available: boolean }> {
    const response = await this.client.get<{ available: boolean }>('/availability/check-times', { params });
    return response.data;
  }

  // Clients
  async listClients(): Promise<Client[]> {
    return this.paginatedGet<Client>('/clients');
  }

  async getClient(id: number): Promise<Client> {
    const response = await this.client.get<Client>(`/clients/${id}`);
    return response.data;
  }

  async createClient(data: {
    firstName: string;
    lastName: string;
    email: string;
    phone?: string;
    notes?: string;
  }): Promise<Client> {
    const response = await this.client.post<Client>('/clients', data);
    return response.data;
  }

  async updateClient(id: number, data: Partial<Client>): Promise<Client> {
    const response = await this.client.put<Client>(`/clients/${id}`, data);
    return response.data;
  }

  async deleteClient(id: number): Promise<void> {
    await this.client.delete(`/clients/${id}`);
  }

  // Calendars
  async listCalendars(): Promise<Calendar[]> {
    const response = await this.client.get<Calendar[]>('/calendars');
    return response.data;
  }

  async getCalendar(id: number): Promise<Calendar> {
    const response = await this.client.get<Calendar>(`/calendars/${id}`);
    return response.data;
  }

  async createCalendar(data: {
    name: string;
    email?: string;
    location?: string;
    timezone?: string;
  }): Promise<Calendar> {
    const response = await this.client.post<Calendar>('/calendars', data);
    return response.data;
  }

  async updateCalendar(id: number, data: Partial<Calendar>): Promise<Calendar> {
    const response = await this.client.put<Calendar>(`/calendars/${id}`, data);
    return response.data;
  }

  // Products
  async listProducts(type?: 'addons' | 'packages' | 'subscriptions' | 'certificates'): Promise<Product[]> {
    const endpoint = type ? `/products/${type}` : '/products';
    const response = await this.client.get<Product[]>(endpoint);
    return response.data;
  }

  async getProduct(id: number): Promise<Product> {
    const response = await this.client.get<Product>(`/products/${id}`);
    return response.data;
  }

  // Forms
  async listForms(): Promise<Form[]> {
    const response = await this.client.get<Form[]>('/forms');
    return response.data;
  }

  async getFormFields(formId: number): Promise<FormField[]> {
    const response = await this.client.get<FormField[]>(`/forms/${formId}/fields`);
    return response.data;
  }

  async getIntakeFormResponses(appointmentId: number): Promise<any> {
    const response = await this.client.get(`/appointments/${appointmentId}/forms`);
    return response.data;
  }

  // Labels
  async listLabels(): Promise<Label[]> {
    const response = await this.client.get<Label[]>('/labels');
    return response.data;
  }

  async createLabel(data: { name: string; color?: string }): Promise<Label> {
    const response = await this.client.post<Label>('/labels', data);
    return response.data;
  }

  async deleteLabel(id: number): Promise<void> {
    await this.client.delete(`/labels/${id}`);
  }

  async addLabelToAppointment(appointmentId: number, labelId: number): Promise<void> {
    await this.client.post(`/appointments/${appointmentId}/labels`, { labelID: labelId });
  }

  async removeLabelFromAppointment(appointmentId: number, labelId: number): Promise<void> {
    await this.client.delete(`/appointments/${appointmentId}/labels/${labelId}`);
  }

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

  async createWebhook(data: {
    event: string;
    url: string;
  }): Promise<Webhook> {
    const response = await this.client.post<Webhook>('/webhooks', data);
    return response.data;
  }

  async deleteWebhook(id: number): Promise<void> {
    await this.client.delete(`/webhooks/${id}`);
  }

  // Coupons
  async listCoupons(): Promise<Coupon[]> {
    const response = await this.client.get<Coupon[]>('/coupons');
    return response.data;
  }

  async getCoupon(id: number): Promise<Coupon> {
    const response = await this.client.get<Coupon>(`/coupons/${id}`);
    return response.data;
  }

  async createCoupon(data: {
    code: string;
    name?: string;
    amountOff?: number;
    percentageOff?: number;
    validFrom?: string;
    validTo?: string;
    maxUses?: number;
  }): Promise<Coupon> {
    const response = await this.client.post<Coupon>('/coupons', data);
    return response.data;
  }

  async updateCoupon(id: number, data: Partial<Coupon>): Promise<Coupon> {
    const response = await this.client.put<Coupon>(`/coupons/${id}`, data);
    return response.data;
  }

  async deleteCoupon(id: number): Promise<void> {
    await this.client.delete(`/coupons/${id}`);
  }

  // Blocks (Time Blocks)
  async listBlocks(params?: {
    calendarID?: number;
    minDate?: string;
    maxDate?: string;
  }): Promise<Block[]> {
    const response = await this.client.get<Block[]>('/blocks', { params });
    return response.data;
  }

  async createBlock(data: {
    calendarID: number;
    start: string;
    end: string;
    notes?: string;
  }): Promise<Block> {
    const response = await this.client.post<Block>('/blocks', data);
    return response.data;
  }

  async deleteBlock(id: number): Promise<void> {
    await this.client.delete(`/blocks/${id}`);
  }
}
