import type {
  AcuityConfig,
  Appointment,
  CreateAppointmentParams,
  UpdateAppointmentParams,
  Calendar,
  AppointmentType,
  CreateAppointmentTypeParams,
  Client,
  CreateClientParams,
  Availability,
  AvailabilityParams,
  Block,
  CreateBlockParams,
  Product,
  CreateProductParams,
  Certificate,
  CreateCertificateParams,
  Coupon,
  CreateCouponParams,
  Form,
  CreateFormParams,
  Label,
  CreateLabelParams,
  Package,
  CreatePackageParams,
  Subscription,
  CreateSubscriptionParams,
  PaginationParams,
} from '../types/index.js';

export class AcuityClient {
  private userId: string;
  private apiKey: string;
  private baseUrl: string;
  private authHeader: string;

  constructor(config: AcuityConfig) {
    this.userId = config.userId;
    this.apiKey = config.apiKey;
    this.baseUrl = config.baseUrl || 'https://acuityscheduling.com/api/v1';
    
    // Basic Auth: base64 encode userId:apiKey
    const credentials = Buffer.from(`${this.userId}:${this.apiKey}`).toString('base64');
    this.authHeader = `Basic ${credentials}`;
  }

  private async request<T>(
    method: string,
    endpoint: string,
    data?: any,
    params?: Record<string, string>
  ): Promise<T> {
    const url = new URL(`${this.baseUrl}${endpoint}`);
    
    if (params) {
      Object.entries(params).forEach(([key, value]) => {
        if (value !== undefined && value !== null) {
          url.searchParams.append(key, value.toString());
        }
      });
    }

    const options: RequestInit = {
      method,
      headers: {
        'Authorization': this.authHeader,
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
    };

    if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
      options.body = JSON.stringify(data);
    }

    try {
      const response = await fetch(url.toString(), options);
      
      if (!response.ok) {
        const errorBody = await response.text();
        let errorMessage = `Acuity API error: ${response.status} ${response.statusText}`;
        
        try {
          const errorJson = JSON.parse(errorBody);
          errorMessage = errorJson.message || errorJson.error || errorMessage;
        } catch {
          errorMessage = errorBody || errorMessage;
        }
        
        throw new Error(errorMessage);
      }

      // Handle 204 No Content
      if (response.status === 204) {
        return {} as T;
      }

      const result = await response.json();
      return result as T;
    } catch (error) {
      if (error instanceof Error) {
        throw error;
      }
      throw new Error(`Unknown error during Acuity API request: ${String(error)}`);
    }
  }

  // APPOINTMENTS
  async getAppointments(params?: PaginationParams): Promise<Appointment[]> {
    const queryParams: Record<string, string> = {};
    if (params?.max) queryParams.max = params.max.toString();
    if (params?.offset) queryParams.offset = params.offset.toString();
    if (params?.minDate) queryParams.minDate = params.minDate;
    if (params?.maxDate) queryParams.maxDate = params.maxDate;
    
    return this.request<Appointment[]>('GET', '/appointments', undefined, queryParams);
  }

  async getAppointment(id: number): Promise<Appointment> {
    return this.request<Appointment>('GET', `/appointments/${id}`);
  }

  async createAppointment(params: CreateAppointmentParams): Promise<Appointment> {
    return this.request<Appointment>('POST', '/appointments', params);
  }

  async updateAppointment(id: number, params: UpdateAppointmentParams): Promise<Appointment> {
    return this.request<Appointment>('PUT', `/appointments/${id}`, params);
  }

  async cancelAppointment(id: number): Promise<Appointment> {
    return this.request<Appointment>('PUT', `/appointments/${id}/cancel`);
  }

  async rescheduleAppointment(id: number, datetime: string): Promise<Appointment> {
    return this.request<Appointment>('PUT', `/appointments/${id}/reschedule`, { datetime });
  }

  // CALENDARS
  async getCalendars(): Promise<Calendar[]> {
    return this.request<Calendar[]>('GET', '/calendars');
  }

  async getCalendar(id: number): Promise<Calendar> {
    return this.request<Calendar>('GET', `/calendars/${id}`);
  }

  // APPOINTMENT TYPES
  async getAppointmentTypes(): Promise<AppointmentType[]> {
    return this.request<AppointmentType[]>('GET', '/appointment-types');
  }

  async getAppointmentType(id: number): Promise<AppointmentType> {
    return this.request<AppointmentType>('GET', `/appointment-types/${id}`);
  }

  async createAppointmentType(params: CreateAppointmentTypeParams): Promise<AppointmentType> {
    return this.request<AppointmentType>('POST', '/appointment-types', params);
  }

  async updateAppointmentType(id: number, params: Partial<CreateAppointmentTypeParams>): Promise<AppointmentType> {
    return this.request<AppointmentType>('PUT', `/appointment-types/${id}`, params);
  }

  async deleteAppointmentType(id: number): Promise<void> {
    return this.request<void>('DELETE', `/appointment-types/${id}`);
  }

  // CLIENTS
  async getClients(params?: PaginationParams): Promise<Client[]> {
    const queryParams: Record<string, string> = {};
    if (params?.max) queryParams.max = params.max.toString();
    if (params?.offset) queryParams.offset = params.offset.toString();
    
    return this.request<Client[]>('GET', '/clients', undefined, queryParams);
  }

  async getClient(id: number): Promise<Client> {
    return this.request<Client>('GET', `/clients/${id}`);
  }

  async createClient(params: CreateClientParams): Promise<Client> {
    return this.request<Client>('POST', '/clients', params);
  }

  async updateClient(id: number, params: Partial<CreateClientParams>): Promise<Client> {
    return this.request<Client>('PUT', `/clients/${id}`, params);
  }

  async deleteClient(id: number): Promise<void> {
    return this.request<void>('DELETE', `/clients/${id}`);
  }

  // AVAILABILITY
  async getAvailability(params: AvailabilityParams): Promise<Availability[]> {
    const queryParams: Record<string, string> = {
      appointmentTypeID: params.appointmentTypeID.toString(),
      month: params.month,
    };
    if (params.calendarID) queryParams.calendarID = params.calendarID.toString();
    if (params.timezone) queryParams.timezone = params.timezone;
    
    return this.request<Availability[]>('GET', '/availability/dates', undefined, queryParams);
  }

  async getAvailableTimes(params: AvailabilityParams & { date: string }): Promise<Availability[]> {
    const queryParams: Record<string, string> = {
      appointmentTypeID: params.appointmentTypeID.toString(),
      date: params.date,
    };
    if (params.calendarID) queryParams.calendarID = params.calendarID.toString();
    if (params.timezone) queryParams.timezone = params.timezone;
    
    return this.request<Availability[]>('GET', '/availability/times', undefined, queryParams);
  }

  // BLOCKS
  async getBlocks(params?: { calendarID?: number }): Promise<Block[]> {
    const queryParams: Record<string, string> = {};
    if (params?.calendarID) queryParams.calendarID = params.calendarID.toString();
    
    return this.request<Block[]>('GET', '/blocks', undefined, queryParams);
  }

  async getBlock(id: number): Promise<Block> {
    return this.request<Block>('GET', `/blocks/${id}`);
  }

  async createBlock(params: CreateBlockParams): Promise<Block> {
    return this.request<Block>('POST', '/blocks', params);
  }

  async updateBlock(id: number, params: Partial<CreateBlockParams>): Promise<Block> {
    return this.request<Block>('PUT', `/blocks/${id}`, params);
  }

  async deleteBlock(id: number): Promise<void> {
    return this.request<void>('DELETE', `/blocks/${id}`);
  }

  // PRODUCTS
  async getProducts(): Promise<Product[]> {
    return this.request<Product[]>('GET', '/products');
  }

  async getProduct(id: number): Promise<Product> {
    return this.request<Product>('GET', `/products/${id}`);
  }

  async createProduct(params: CreateProductParams): Promise<Product> {
    return this.request<Product>('POST', '/products', params);
  }

  async updateProduct(id: number, params: Partial<CreateProductParams>): Promise<Product> {
    return this.request<Product>('PUT', `/products/${id}`, params);
  }

  async deleteProduct(id: number): Promise<void> {
    return this.request<void>('DELETE', `/products/${id}`);
  }

  // CERTIFICATES
  async getCertificates(): Promise<Certificate[]> {
    return this.request<Certificate[]>('GET', '/certificates');
  }

  async getCertificate(code: string): Promise<Certificate> {
    return this.request<Certificate>('GET', `/certificates/${code}`);
  }

  async createCertificate(params: CreateCertificateParams): Promise<Certificate> {
    return this.request<Certificate>('POST', '/certificates', params);
  }

  async deleteCertificate(code: string): Promise<void> {
    return this.request<void>('DELETE', `/certificates/${code}`);
  }

  // COUPONS
  async getCoupons(): Promise<Coupon[]> {
    return this.request<Coupon[]>('GET', '/coupons');
  }

  async getCoupon(id: number): Promise<Coupon> {
    return this.request<Coupon>('GET', `/coupons/${id}`);
  }

  async createCoupon(params: CreateCouponParams): Promise<Coupon> {
    return this.request<Coupon>('POST', '/coupons', params);
  }

  async updateCoupon(id: number, params: Partial<CreateCouponParams>): Promise<Coupon> {
    return this.request<Coupon>('PUT', `/coupons/${id}`, params);
  }

  async deleteCoupon(id: number): Promise<void> {
    return this.request<void>('DELETE', `/coupons/${id}`);
  }

  // FORMS
  async getForms(): Promise<Form[]> {
    return this.request<Form[]>('GET', '/forms');
  }

  async getForm(id: number): Promise<Form> {
    return this.request<Form>('GET', `/forms/${id}`);
  }

  async createForm(params: CreateFormParams): Promise<Form> {
    return this.request<Form>('POST', '/forms', params);
  }

  async updateForm(id: number, params: Partial<CreateFormParams>): Promise<Form> {
    return this.request<Form>('PUT', `/forms/${id}`, params);
  }

  async deleteForm(id: number): Promise<void> {
    return this.request<void>('DELETE', `/forms/${id}`);
  }

  // LABELS
  async getLabels(): Promise<Label[]> {
    return this.request<Label[]>('GET', '/labels');
  }

  async getLabel(id: number): Promise<Label> {
    return this.request<Label>('GET', `/labels/${id}`);
  }

  async createLabel(params: CreateLabelParams): Promise<Label> {
    return this.request<Label>('POST', '/labels', params);
  }

  async updateLabel(id: number, params: Partial<CreateLabelParams>): Promise<Label> {
    return this.request<Label>('PUT', `/labels/${id}`, params);
  }

  async deleteLabel(id: number): Promise<void> {
    return this.request<void>('DELETE', `/labels/${id}`);
  }

  // PACKAGES
  async getPackages(): Promise<Package[]> {
    return this.request<Package[]>('GET', '/packages');
  }

  async getPackage(id: number): Promise<Package> {
    return this.request<Package>('GET', `/packages/${id}`);
  }

  async createPackage(params: CreatePackageParams): Promise<Package> {
    return this.request<Package>('POST', '/packages', params);
  }

  async updatePackage(id: number, params: Partial<CreatePackageParams>): Promise<Package> {
    return this.request<Package>('PUT', `/packages/${id}`, params);
  }

  async deletePackage(id: number): Promise<void> {
    return this.request<void>('DELETE', `/packages/${id}`);
  }

  // SUBSCRIPTIONS
  async getSubscriptions(): Promise<Subscription[]> {
    return this.request<Subscription[]>('GET', '/subscriptions');
  }

  async getSubscription(id: number): Promise<Subscription> {
    return this.request<Subscription>('GET', `/subscriptions/${id}`);
  }

  async createSubscription(params: CreateSubscriptionParams): Promise<Subscription> {
    return this.request<Subscription>('POST', '/subscriptions', params);
  }

  async updateSubscription(id: number, params: Partial<CreateSubscriptionParams>): Promise<Subscription> {
    return this.request<Subscription>('PUT', `/subscriptions/${id}`, params);
  }

  async deleteSubscription(id: number): Promise<void> {
    return this.request<void>('DELETE', `/subscriptions/${id}`);
  }
}
