import {
  AddigyConfig,
  AddigyDevice,
  AddigyPolicy,
  AddigyOrganization,
  AddigyDeviceWithApps,
  AddigyApplication,
  AddigyAlert,
  AddigyMaintenanceItem,
  AddigyMonitoringItem,
  AddigyCustomFact,
  AddigySoftwareItem,
  AddigyVariable,
  AddigyApiResponse,
  AddigyApiError,
  QueryParams,
} from '@/lib/types/addigy';

export class AddigyClient {
  private config: AddigyConfig;
  private rateLimiter: RateLimiter;

  constructor(config: AddigyConfig) {
    this.config = config;
    // Addigy rate limit: 1000 requests per 10 seconds = 100 requests per second
    this.rateLimiter = new RateLimiter(100);
  }

  private getAuthHeaders(): Record<string, string> {
    return {
      'x-api-key': this.config.apiToken,
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    };
  }

  private async makeApiCall<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    await this.rateLimiter.throttle();

    const url = `${this.config.apiUrl}${endpoint}`;

    try {
      const response = await fetch(url, {
        ...options,
        headers: {
          ...this.getAuthHeaders(),
          ...options.headers,
        },
      });

      const responseText = await response.text();

      if (!response.ok) {
        console.error(`Addigy API error: ${response.status} - ${responseText}`);

        let errorMessage = `API Error: ${response.statusText}`;
        try {
          const errorData: AddigyApiError = JSON.parse(responseText);
          errorMessage = errorData.message || errorData.error || errorMessage;
        } catch (parseError) {
          errorMessage = responseText || errorMessage;
        }

        throw new Error(errorMessage);
      }

      try {
        return JSON.parse(responseText);
      } catch (parseError) {
        console.error('Failed to parse API response:', parseError);
        throw new Error('Invalid JSON response from Addigy API');
      }
    } catch (error) {
      console.error('API call failed:', error);
      throw error;
    }
  }

  private buildQueryString(params: QueryParams): string {
    const queryParts: string[] = [];

    if (params.page !== undefined) {
      queryParts.push(`page=${params.page}`);
    }

    if (params.limit !== undefined) {
      queryParts.push(`limit=${params.limit}`);
    }

    if (params.filters && params.filters.length > 0) {
      const filtersJson = JSON.stringify({ filters: params.filters });
      queryParts.push(`filters=${encodeURIComponent(filtersJson)}`);
    }

    return queryParts.length > 0 ? `?${queryParts.join('&')}` : '';
  }

  // Device Methods
  async getAllDevices(params: QueryParams = {}): Promise<AddigyDevice[]> {
    // Get organization ID - either from config or fetch from policies
    let orgId = this.config.organizationId;
    
    if (!orgId) {
      // Fetch first policy to get orgid
      const policies = await this.getAllPolicies({ limit: 1 });
      if (policies.length > 0) {
        orgId = policies[0].orgid;
        console.log('Using orgid from first policy:', orgId);
      } else {
        throw new Error('No organization ID configured and no policies found');
      }
    }

    // Addigy v2 /o/{orgid}/devices requires POST with JSON body
    const body: any = {
      page: params.page || 1,
      per_page: params.limit || 500,
    };

    // Add filters if provided
    if (params.filters && params.filters.length > 0) {
      body.query = {
        filters: params.filters,
      };
    }

    console.log('Addigy devices request body:', JSON.stringify(body));
    console.log('Fetching devices from:', `${this.config.apiUrl}/o/${orgId}/devices`);

    const response = await this.makeApiCall<{ items?: any[]; data?: any[]; records?: any[] }>(
      `/o/${orgId}/devices`,
      {
        method: 'POST',
        body: JSON.stringify(body),
      }
    );

    console.log('Addigy response keys:', Object.keys(response));
    console.log('Addigy raw items count:', response.items?.length || 0);

    // Transform Addigy's nested facts structure to flat device objects
    const rawItems = response.items || response.data || response.records || [];
    
    // Field name mapping from Addigy to UI-expected format
    const fieldMapping: Record<string, string> = {
      'device_name': 'Device Name',
      'device_model_name': 'Device Model Name',
      'serial_number': 'Serial Number',
      'mac_os_x_version': 'MAC OS X Version',
      'ios_version': 'iOS Version',
      'current_user': 'Current User',
      'free_disk_percentage': 'Free Disk Percentage',
      'battery_percentage': 'Battery Percentage',
      'firewall_enabled': 'Firewall Enabled',
      'filevault_enabled': 'FileVault Enabled',
      'agent_version': 'Agent Version',
    };
    
    const devices: AddigyDevice[] = rawItems.map((item: any) => {
      const device: any = {};
      
      // Extract values from nested facts structure
      if (item.facts) {
        for (const [key, factObj] of Object.entries(item.facts)) {
          const fact = factObj as any;
          if (fact && typeof fact === 'object' && 'value' in fact) {
            // Use mapped field name if available, otherwise use original
            const mappedKey = fieldMapping[key] || key;
            device[mappedKey] = fact.value;
          }
        }
      }
      
      return device as AddigyDevice;
    });

    console.log('Transformed devices count:', devices.length);
    if (devices.length > 0) {
      console.log('First device keys:', Object.keys(devices[0]).slice(0, 10));
    }

    return devices;
  }

  async getDeviceById(deviceId: string): Promise<AddigyDevice | null> {
    try {
      const response = await this.makeApiCall<AddigyApiResponse<AddigyDevice>>(
        `/devices/${deviceId}`,
        { method: 'GET' }
      );

      return response.data || null;
    } catch (error) {
      console.error(`Failed to get device ${deviceId}:`, error);
      return null;
    }
  }

  async getDevicesByPolicy(policyId: string): Promise<AddigyDevice[]> {
    return this.getAllDevices({
      filters: [
        {
          audit_field: 'policy_id',
          type: 'string',
          operation: 'equals',
          value: policyId,
        },
      ],
    });
  }

  async getOnlineDevices(): Promise<AddigyDevice[]> {
    return this.getAllDevices({
      filters: [
        {
          audit_field: 'online',
          type: 'boolean',
          operation: 'equals',
          value: true,
        },
      ],
    });
  }

  async getDeviceApplications(deviceId: string): Promise<AddigyApplication[]> {
    try {
      const response = await this.makeApiCall<{ items?: AddigyApplication[]; data?: AddigyApplication[] }>(
        `/devices/${deviceId}/applications`,
        { method: 'GET' }
      );

      return response.items || response.data || [];
    } catch (error) {
      console.error(`Failed to get applications for device ${deviceId}:`, error);
      return [];
    }
  }

  // Policy Methods
  async getAllPolicies(params: QueryParams = {}): Promise<AddigyPolicy[]> {
    const body: any = {
      page: params.page || 1,
      per_page: params.limit || 100,
    };

    console.log('Fetching policies from:', `${this.config.apiUrl}/oa/policies/query`);

    // Addigy policies endpoint returns array directly, not wrapped in items/data
    const response = await this.makeApiCall<AddigyPolicy[]>(
      `/oa/policies/query`,
      {
        method: 'POST',
        body: JSON.stringify(body),
      }
    );

    console.log('Policies response type:', Array.isArray(response) ? 'array' : typeof response);
    console.log('Policies count:', Array.isArray(response) ? response.length : 0);

    // Response is already an array
    return Array.isArray(response) ? response : [];
  }

  async getPolicyById(policyId: string): Promise<AddigyPolicy | null> {
    try {
      const response = await this.makeApiCall<AddigyApiResponse<AddigyPolicy>>(
        `/policies/${policyId}`,
        { method: 'GET' }
      );

      return response.data || null;
    } catch (error) {
      console.error(`Failed to get policy ${policyId}:`, error);
      return null;
    }
  }

  async createPolicy(policy: Partial<AddigyPolicy>): Promise<AddigyPolicy> {
    const response = await this.makeApiCall<AddigyApiResponse<AddigyPolicy>>(
      '/policies',
      {
        method: 'POST',
        body: JSON.stringify(policy),
      }
    );

    if (!response.data) {
      throw new Error('Failed to create policy');
    }

    return response.data;
  }

  async updatePolicy(
    policyId: string,
    updates: Partial<AddigyPolicy>
  ): Promise<AddigyPolicy> {
    const response = await this.makeApiCall<AddigyApiResponse<AddigyPolicy>>(
      `/policies/${policyId}`,
      {
        method: 'PATCH',
        body: JSON.stringify(updates),
      }
    );

    if (!response.data) {
      throw new Error('Failed to update policy');
    }

    return response.data;
  }

  // Organization Methods
  async getOrganizations(params: QueryParams = {}): Promise<AddigyOrganization[]> {
    const queryString = this.buildQueryString(params);
    const response = await this.makeApiCall<{ items?: AddigyOrganization[]; data?: AddigyOrganization[] }>(
      `/organizations${queryString}`,
      { method: 'GET' }
    );

    return response.items || response.data || [];
  }

  async getOrganizationById(orgId: string): Promise<AddigyOrganization | null> {
    try {
      const response = await this.makeApiCall<AddigyApiResponse<AddigyOrganization>>(
        `/organizations/${orgId}`,
        { method: 'GET' }
      );

      return response.data || null;
    } catch (error) {
      console.error(`Failed to get organization ${orgId}:`, error);
      return null;
    }
  }

  // Alert Methods
  async getAlerts(params: QueryParams = {}): Promise<AddigyAlert[]> {
    const queryString = this.buildQueryString(params);
    const response = await this.makeApiCall<{ items?: AddigyAlert[]; data?: AddigyAlert[] }>(
      `/alerts${queryString}`,
      { method: 'GET' }
    );

    return response.items || response.data || [];
  }

  async getActiveAlerts(): Promise<AddigyAlert[]> {
    return this.getAlerts({
      filters: [
        {
          audit_field: 'resolved',
          type: 'boolean',
          operation: 'equals',
          value: false,
        },
      ],
    });
  }

  async resolveAlert(alertId: string): Promise<void> {
    await this.makeApiCall(`/alerts/${alertId}/resolve`, {
      method: 'POST',
    });
  }

  // Maintenance Methods
  async getMaintenanceItems(params: QueryParams = {}): Promise<AddigyMaintenanceItem[]> {
    const queryString = this.buildQueryString(params);
    const response = await this.makeApiCall<{ items?: AddigyMaintenanceItem[]; data?: AddigyMaintenanceItem[] }>(
      `/maintenance${queryString}`,
      { method: 'GET' }
    );

    return response.items || response.data || [];
  }

  // Monitoring Methods
  async getMonitoringItems(params: QueryParams = {}): Promise<AddigyMonitoringItem[]> {
    const queryString = this.buildQueryString(params);
    const response = await this.makeApiCall<{ items?: AddigyMonitoringItem[]; data?: AddigyMonitoringItem[] }>(
      `/monitoring${queryString}`,
      { method: 'GET' }
    );

    return response.items || response.data || [];
  }

  // Custom Facts / Variables
  async getCustomFacts(deviceId: string): Promise<AddigyCustomFact[]> {
    try {
      const response = await this.makeApiCall<{ items?: AddigyCustomFact[]; data?: AddigyCustomFact[] }>(
        `/devices/${deviceId}/facts`,
        { method: 'GET' }
      );

      return response.items || response.data || [];
    } catch (error) {
      console.error(`Failed to get custom facts for device ${deviceId}:`, error);
      return [];
    }
  }

  async createCustomFact(
    deviceId: string,
    fact: Partial<AddigyCustomFact>
  ): Promise<AddigyCustomFact> {
    const response = await this.makeApiCall<AddigyApiResponse<AddigyCustomFact>>(
      `/devices/${deviceId}/facts`,
      {
        method: 'POST',
        body: JSON.stringify(fact),
      }
    );

    if (!response.data) {
      throw new Error('Failed to create custom fact');
    }

    return response.data;
  }

  async getVariables(params: QueryParams = {}): Promise<AddigyVariable[]> {
    const queryString = this.buildQueryString(params);
    const response = await this.makeApiCall<{ items?: AddigyVariable[]; data?: AddigyVariable[] }>(
      `/variables${queryString}`,
      { method: 'GET' }
    );

    return response.items || response.data || [];
  }

  // Software Methods
  async getSoftwareItems(params: QueryParams = {}): Promise<AddigySoftwareItem[]> {
    const queryString = this.buildQueryString(params);
    const response = await this.makeApiCall<{ items?: AddigySoftwareItem[]; data?: AddigySoftwareItem[] }>(
      `/software${queryString}`,
      { method: 'GET' }
    );

    return response.items || response.data || [];
  }

  async getSoftwareVersions(softwareIdentifier: string): Promise<string[]> {
    try {
      const response = await this.makeApiCall<AddigyApiResponse<{ versions: string[] }>>(
        `/software/${softwareIdentifier}/versions`,
        { method: 'GET' }
      );

      return response.data?.versions || [];
    } catch (error) {
      console.error(`Failed to get versions for software ${softwareIdentifier}:`, error);
      return [];
    }
  }

  // Device Actions
  async assignDeviceToPolicy(deviceId: string, policyId: string): Promise<void> {
    await this.makeApiCall(`/devices/${deviceId}/policy`, {
      method: 'PATCH',
      body: JSON.stringify({ policy_id: policyId }),
    });
  }

  async removeDevice(deviceId: string): Promise<void> {
    await this.makeApiCall(`/devices/${deviceId}`, {
      method: 'DELETE',
    });
  }

  // Utility Methods
  async testConnection(): Promise<boolean> {
    try {
      await this.getAllPolicies({ limit: 1 });
      return true;
    } catch (error) {
      console.error('Addigy connection test failed:', error);
      return false;
    }
  }

  // Paginated query to handle large datasets
  async getAllDevicesPaginated(pageSize: number = 100): Promise<AddigyDevice[]> {
    const allDevices: AddigyDevice[] = [];
    let page = 1;
    let hasMore = true;

    while (hasMore) {
      console.log(`Fetching devices page ${page} (max ${pageSize} records)...`);

      const body = {
        page,
        per_page: pageSize,
      };

      const response = await this.makeApiCall<{ items?: AddigyDevice[]; data?: AddigyDevice[]; pages?: number }>(
        `/devices`,
        {
          method: 'POST',
          body: JSON.stringify(body),
        }
      );

      const devices = response.items || response.data || [];
      allDevices.push(...devices);

      console.log(`Fetched ${devices.length} devices, total so far: ${allDevices.length}`);

      // Check if we have more pages
      hasMore = devices.length === pageSize && (!response.pages || page < response.pages);
      page++;

      // Safety limit to prevent infinite loops
      if (page > 100) {
        console.warn('Reached page limit (100) for devices');
        break;
      }
    }

    console.log(`Total devices fetched: ${allDevices.length}`);
    return allDevices;
  }
}

// Rate Limiter class
class RateLimiter {
  private maxRequestsPerSecond: number;
  private requestTimes: number[];

  constructor(maxRequestsPerSecond = 100) {
    this.maxRequestsPerSecond = maxRequestsPerSecond;
    this.requestTimes = [];
  }

  async throttle(): Promise<void> {
    const now = Date.now();
    const oneSecondAgo = now - 1000;

    // Remove old request times
    this.requestTimes = this.requestTimes.filter((t) => t > oneSecondAgo);

    // If at limit, wait
    if (this.requestTimes.length >= this.maxRequestsPerSecond) {
      const oldestRequest = this.requestTimes[0];
      const waitTime = 1000 - (now - oldestRequest);
      if (waitTime > 0) {
        await new Promise((resolve) => setTimeout(resolve, waitTime));
      }
    }

    this.requestTimes.push(Date.now());
  }
}
