import {
  DattoRMMConfig,
  DattoRMMDevice,
  DattoRMMSite,
  DattoRMMAlert,
  DattoRMMApiResponse,
  DattoRMMError,
} from '@/lib/types/datto-rmm';

export class DattoRMMClient {
  private config: DattoRMMConfig;
  private accessToken: string | null = null;
  private tokenExpiry: Date | null = null;

  constructor(config: DattoRMMConfig) {
    this.config = config;
  }

  /**
   * Get OAuth2 access token
   * Datto RMM API v2 uses OAuth2 with password grant type
   * The API Access Key is the username and API Secret Key is the password
   */
  private async getAccessToken(): Promise<string> {
    // Check if we have a valid token
    if (this.accessToken && this.tokenExpiry && this.tokenExpiry > new Date()) {
      return this.accessToken;
    }

    // OAuth2 token endpoint
    const authUrl = 'https://concord-api.centrastage.net/auth/oauth/token';
    
    // According to Datto RMM API v2 docs:
    // - Basic auth with client_id: "public-client" and client_secret: "public"
    // - grant_type: "password"
    // - username: Your API Access Key
    // - password: Your API Secret Key
    const clientCredentials = Buffer.from('public-client:public').toString('base64');
    
    const formData = new URLSearchParams();
    formData.append('grant_type', 'password');
    formData.append('username', this.config.apiKey);
    formData.append('password', this.config.apiSecret);

    try {
      console.log('Authenticating with Datto RMM API v2...');
      const response = await fetch(authUrl, {
        method: 'POST',
        headers: {
          'Authorization': `Basic ${clientCredentials}`,
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: formData.toString(),
      });

      if (!response.ok) {
        const error = await response.text();
        console.error('Datto RMM auth failed:', response.status, error);
        throw new Error(`Authentication failed: ${response.status} - ${error}`);
      }

      const responseText = await response.text();
      let data;
      try {
        data = JSON.parse(responseText);
      } catch (parseError) {
        console.error('Failed to parse auth response. Response was:', responseText.substring(0, 500));
        throw new Error('Invalid response from auth server - expected JSON but got HTML/text');
      }
      
      this.accessToken = data.access_token;
      
      // Set token expiry (usually 1 hour, but we'll refresh after 50 minutes to be safe)
      this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
      
      console.log('Successfully authenticated with Datto RMM');
      return this.accessToken as string;
    } catch (error) {
      console.error('Failed to get Datto RMM access token:', error);
      throw error;
    }
  }

  /**
   * Make an authenticated API request
   */
  private async makeApiCall<T>(
    endpoint: string,
    options: RequestInit = {}
  ): Promise<T> {
    const token = await this.getAccessToken();
    
    // Use the base URL directly with v2 API
    const url = `https://concord-api.centrastage.net/api/v2${endpoint}`;
    
    const response = await fetch(url, {
      ...options,
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        ...options.headers,
      },
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error(`API call failed: ${response.status} ${response.statusText}`);
      console.error('Error response:', errorText);
      
      let errorMessage = `API Error: ${response.status}`;
      
      try {
        const errorData = JSON.parse(errorText) as DattoRMMError;
        errorMessage = `${errorData.error}: ${errorData.message}`;
      } catch {
        errorMessage += ` - ${errorText.substring(0, 200)}`;
      }
      
      throw new Error(errorMessage);
    }

    const responseText = await response.text();
    if (!responseText) {
      return {} as T;
    }

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

  /**
   * Get all sites
   */
  async getSites(): Promise<DattoRMMSite[]> {
    const response = await this.makeApiCall<any>(
      '/account/sites',
      { method: 'GET' }
    );
    
    // The API returns sites in a 'sites' field
    return response.sites || [];
  }

  /**
   * Get site by name (for matching with Autotask company)
   */
  async getSiteByName(name: string): Promise<DattoRMMSite | null> {
    const sites = await this.getSites();
    const site = sites.find(s => 
      s.name.toLowerCase() === name.toLowerCase() ||
      s.name.toLowerCase().includes(name.toLowerCase())
    );
    
    return site || null;
  }

  /**
   * Get all devices (with pagination support)
   */
  async getAllDevices(): Promise<DattoRMMDevice[]> {
    const allDevices: DattoRMMDevice[] = [];
    let page = 1;
    const pageSize = 250; // Datto RMM default page size
    
    while (true) {
      const response = await this.makeApiCall<any>(
        `/account/devices?page=${page}&pageSize=${pageSize}`,
        { method: 'GET' }
      );
      
      const devices = response.devices || [];
      allDevices.push(...devices);
      
      console.log(`Fetched page ${page}: ${devices.length} devices (total: ${allDevices.length})`);
      
      // If we got less than pageSize devices, we've reached the end
      if (devices.length < pageSize) {
        break;
      }
      
      page++;
    }
    
    console.log(`Total RMM devices fetched: ${allDevices.length}`);
    return allDevices;
  }

  /**
   * Get devices for a specific site
   */
  async getDevicesBySite(siteUid: string): Promise<DattoRMMDevice[]> {
    const response = await this.makeApiCall<any>(
      `/site/${siteUid}/devices`,
      { method: 'GET' }
    );
    
    // The API returns devices in a 'devices' field
    return response.devices || [];
  }

  /**
   * Get devices by site name (matches with company name)
   * @deprecated Use getDevicesByCompanyId with site mappings instead
   */
  async getDevicesByCompanyName(companyName: string): Promise<DattoRMMDevice[]> {
    // First, find the site that matches the company name
    const site = await this.getSiteByName(companyName);
    
    if (!site) {
      console.warn(`No RMM site found for company: ${companyName}`);
      return [];
    }
    
    // Then get devices for that site using the UID
    return this.getDevicesBySite(site.uid);
  }

  /**
   * Get devices for multiple sites (for multi-site support)
   */
  async getDevicesForSites(siteUids: string[]): Promise<DattoRMMDevice[]> {
    if (siteUids.length === 0) {
      return [];
    }

    const allDevices: DattoRMMDevice[] = [];
    
    // Fetch devices from all sites in parallel
    const promises = siteUids.map(uid => this.getDevicesBySite(uid));
    const results = await Promise.allSettled(promises);
    
    for (const result of results) {
      if (result.status === 'fulfilled') {
        allDevices.push(...result.value);
      } else {
        console.error('Failed to fetch devices for a site:', result.reason);
      }
    }
    
    return allDevices;
  }

  /**
   * Get device by ID
   */
  async getDeviceById(deviceId: string): Promise<DattoRMMDevice | null> {
    try {
      const response = await this.makeApiCall<{ item: DattoRMMDevice }>(
        `/devices/${deviceId}`,
        { method: 'GET' }
      );
      
      return response.item || null;
    } catch (error) {
      console.error(`Failed to get device ${deviceId}:`, error);
      return null;
    }
  }

  /**
   * Search devices by various criteria
   */
  async searchDevices(criteria: {
    hostname?: string;
    serialNumber?: string;
    ipAddress?: string;
    macAddress?: string;
  }): Promise<DattoRMMDevice[]> {
    const allDevices = await this.getAllDevices();
    
    return allDevices.filter(device => {
      if (criteria.hostname && 
          !device.hostname.toLowerCase().includes(criteria.hostname.toLowerCase())) {
        return false;
      }
      
      if (criteria.serialNumber && 
          device.serialNumber !== criteria.serialNumber) {
        return false;
      }
      
      if (criteria.ipAddress && 
          device.intIpAddress !== criteria.ipAddress && 
          device.extIpAddress !== criteria.ipAddress) {
        return false;
      }
      
      if (criteria.macAddress && 
          !device.macAddresses?.some(mac => 
            mac.toLowerCase() === criteria.macAddress!.toLowerCase()
          )) {
        return false;
      }
      
      return true;
    });
  }

  /**
   * Get device audit data with detailed hardware information
   */
  async getDeviceAudit(deviceId: string | number): Promise<any> {
    try {
      const response = await this.makeApiCall<any>(
        `/device/${deviceId}/auditdata`,
        { method: 'GET' }
      );
      
      return response;
    } catch (error) {
      console.error(`Failed to get audit for device ${deviceId}:`, error);
      return null;
    }
  }
  
  /**
   * Get device with audit data
   */
  async getDeviceWithAudit(deviceId: string | number): Promise<DattoRMMDevice | null> {
    try {
      // Get basic device info
      const device = await this.getDeviceById(deviceId.toString());
      if (!device) return null;
      
      // Try to get audit data for more details
      const audit = await this.getDeviceAudit(deviceId);
      if (audit) {
        // Merge audit data into device
        if (audit.bios) {
          device.manufacturer = audit.bios.manufacturer || device.manufacturer;
          device.model = audit.bios.model || device.model;
          device.serialNumber = audit.bios.serialNumber || device.serialNumber;
        }
        
        if (audit.system) {
          device.manufacturer = audit.system.manufacturer || device.manufacturer;
          device.model = audit.system.model || device.model;
        }
        
        if (audit.processors && audit.processors.length > 0) {
          device.cpuName = audit.processors[0].name;
          device.cpuCores = audit.processors[0].cores;
        }
        
        if (audit.memory) {
          device.memory = audit.memory.totalPhysicalMemory;
        }
        
        if (audit.disks && audit.disks.length > 0) {
          // Sum up all disk sizes
          device.diskSize = audit.disks.reduce((total: number, disk: any) => 
            total + (disk.size || 0), 0
          );
        }
      }
      
      return device;
    } catch (error) {
      console.error(`Failed to get device with audit ${deviceId}:`, error);
      return null;
    }
  }

  /**
   * Get device alerts
   */
  async getDeviceAlerts(deviceId: string): Promise<any[]> {
    try {
      const response = await this.makeApiCall<DattoRMMApiResponse<any>>(
        `/devices/${deviceId}/alerts`,
        { method: 'GET' }
      );
      
      return response.items || [];
    } catch (error) {
      console.error(`Failed to get alerts for device ${deviceId}:`, error);
      return [];
    }
  }

  /**
   * Generic paginated fetch — follows nextPageUrl until exhausted
   */
  private async fetchAllPages<T>(
    endpoint: string,
    dataKey: string,
    pageSize = 250
  ): Promise<T[]> {
    const allItems: T[] = [];
    let url: string | null = `https://concord-api.centrastage.net/api/v2${endpoint}?pageSize=${pageSize}`;

    while (url) {
      const token = await this.getAccessToken();
      const resp: Response = await fetch(url, {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
      });

      if (!resp.ok) {
        const errText = await resp.text();
        throw new Error(`Datto RMM API ${resp.status}: ${errText.substring(0, 200)}`);
      }

      const body: any = await resp.json();
      const items = body[dataKey] || [];
      allItems.push(...items);

      const nextUrl: string | null = body.pageDetails?.nextPageUrl ?? null;
      console.log(`[DATTO-RMM] ${dataKey}: fetched ${allItems.length} (page had ${items.length})`);

      url = nextUrl;
    }

    return allItems;
  }

  /**
   * Get all sites (paginated)
   */
  async getAllSites(): Promise<DattoRMMSite[]> {
    return this.fetchAllPages<DattoRMMSite>('/account/sites', 'sites');
  }

  /**
   * Get all open alerts (paginated)
   */
  async getAllOpenAlerts(): Promise<DattoRMMAlert[]> {
    return this.fetchAllPages<DattoRMMAlert>('/account/alerts/open', 'alerts');
  }

  /**
   * Get all resolved alerts (paginated, recent only)
   */
  async getRecentResolvedAlerts(maxPages = 4): Promise<DattoRMMAlert[]> {
    const allAlerts: DattoRMMAlert[] = [];
    let url: string | null = 'https://concord-api.centrastage.net/api/v2/account/alerts/resolved?pageSize=250';
    let pages = 0;

    while (url && pages < maxPages) {
      const token = await this.getAccessToken();
      const resp: Response = await fetch(url, {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
      });

      if (!resp.ok) break;

      const body: any = await resp.json();
      const alerts = body.alerts || [];
      allAlerts.push(...alerts);
      url = body.pageDetails?.nextPageUrl ?? null;
      pages++;
      console.log(`[DATTO-RMM] resolved alerts: fetched ${allAlerts.length} (page ${pages})`);
    }

    return allAlerts;
  }

  /**
   * Get all available automation components (scripts/tasks) for quick jobs.
   * GET /api/v2/account/components
   */
  async getComponents(): Promise<any[]> {
    const allComponents: any[] = [];
    let page = 1;
    const max = 100;

    while (true) {
      const response = await this.makeApiCall<any>(
        `/account/components?page=${page}&max=${max}`,
        { method: 'GET' }
      );

      const components = response.components || [];
      allComponents.push(...components);

      if (components.length < max) break;
      page++;
    }

    return allComponents;
  }

  /**
   * Find a Datto RMM component whose name matches the given regex
   * (case-insensitive). Returns the first match, or null.
   *
   * Generic helper used to auto-discover both the Overshell component
   * (Phase 4.2) and the LogLift collector (Phase 4.3) without admins
   * needing to paste UIDs.
   */
  async findComponentByName(
    pattern: RegExp
  ): Promise<{ uid: string; name: string } | null> {
    const components = await this.getComponents();
    for (const c of components) {
      const name: string =
        c?.name ?? c?.componentName ?? c?.displayName ?? '';
      const uid: string = c?.uid ?? c?.componentUid ?? c?.id ?? '';
      if (!uid) continue;
      if (pattern.test(name)) {
        return { uid, name };
      }
    }
    return null;
  }

  /**
   * Back-compat alias — Phase 4.2 callers expect this name. Defaults to
   * `/overshell/i`. Equivalent to findComponentByName(/overshell/i).
   */
  async findOvershellComponent(
    pattern: RegExp = /overshell/i
  ): Promise<{ uid: string; name: string } | null> {
    return this.findComponentByName(pattern);
  }

  /**
   * Run a quick job on a device.
   * PUT /api/v2/device/{deviceUid}/quickjob
   */
  async runQuickJob(
    deviceUid: string,
    payload: {
      jobName: string;
      jobComponent: {
        componentUid: string;
        variables?: Array<{ name: string; value: string }>;
      };
    }
  ): Promise<any> {
    const token = await this.getAccessToken();
    const url = `https://concord-api.centrastage.net/api/v2/device/${deviceUid}/quickjob`;

    const resp = await fetch(url, {
      method: 'PUT',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
      body: JSON.stringify(payload),
    });

    if (!resp.ok) {
      const errText = await resp.text();
      throw new Error(`Datto RMM quick job failed (${resp.status}): ${errText.substring(0, 200)}`);
    }

    const text = await resp.text();
    return text ? JSON.parse(text) : {};
  }

  /**
   * Get job results for a device.
   * GET /api/v2/job/{jobUid}/results/device/{deviceUid}
   */
  async getJobResults(jobUid: string, deviceUid: string): Promise<any> {
    const token = await this.getAccessToken();
    const url = `https://concord-api.centrastage.net/api/v2/job/${jobUid}/results/${deviceUid}`;

    const resp = await fetch(url, {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
    });

    if (!resp.ok) {
      const errText = await resp.text();
      throw new Error(`Datto RMM job results failed (${resp.status}): ${errText.substring(0, 200)}`);
    }

    return resp.json();
  }

  /**
   * For a PING alert, fetch the ping target (instanceName) from alertContext.
   * Returns null if not found or API call fails.
   */
  async getPingAlertTarget(alertUid: string): Promise<string | null> {
    const token = await this.getAccessToken();
    const url = `${this.config.apiUrl}/api/v2/alert/${alertUid}`;
    const resp = await fetch(url, {
      headers: {
        'Authorization': `Bearer ${token}`,
        'Accept': 'application/json',
      },
    });

    if (!resp.ok) return null;

    const data = await resp.json();
    const ctx = data?.alertContext;
    if (ctx?.['@class'] === 'ping_ctx' && ctx?.instanceName) {
      return ctx.instanceName as string;
    }
    return null;
  }
}
