import {
  AuvikClientConfig,
  AuvikDevice,
  AuvikDeviceResponse,
  AuvikTenant,
  AuvikTenantResponse,
} from '../types/auvik';

export class AuvikClient {
  private config: AuvikClientConfig;
  private requestCount: number = 0;
  private requestTimestamps: number[] = [];
  private readonly RATE_LIMIT = 1000; // requests per hour
  private readonly RATE_LIMIT_WINDOW = 3600000; // 1 hour in milliseconds

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

  /**
   * Get Basic Authentication headers
   */
  private getAuthHeaders(): HeadersInit {
    const credentials = Buffer.from(
      `${this.config.apiUser}:${this.config.apiKey}`
    ).toString('base64');

    return {
      Authorization: `Basic ${credentials}`,
      Accept: 'application/json',
      'Content-Type': 'application/json',
    };
  }

  /**
   * Check and enforce rate limiting
   */
  private checkRateLimit(): void {
    const now = Date.now();
    // Remove timestamps older than 1 hour
    this.requestTimestamps = this.requestTimestamps.filter(
      (timestamp) => now - timestamp < this.RATE_LIMIT_WINDOW
    );

    if (this.requestTimestamps.length >= this.RATE_LIMIT) {
      console.warn(
        `Auvik API rate limit approaching: ${this.requestTimestamps.length}/${this.RATE_LIMIT} requests in the last hour`
      );
    }

    this.requestTimestamps.push(now);
    this.requestCount++;
  }

  /**
   * Make a generic API call with error handling
   */
  private async makeApiCall<T>(url: string, options: RequestInit = {}): Promise<T> {
    this.checkRateLimit();

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

      if (!response.ok) {
        const errorText = await response.text();
        console.error(
          `Auvik API error: ${response.status} ${response.statusText}`,
          errorText
        );
        throw new Error(
          `Auvik API request failed: ${response.status} ${response.statusText}`
        );
      }

      return await response.json();
    } catch (error) {
      console.error('Auvik API call failed:', error);
      throw error;
    }
  }

  /**
   * Get all tenants
   */
  async getTenants(): Promise<AuvikTenant[]> {
    try {
      const url = `${this.config.apiUrl}/v1/tenants`;
      console.log('Fetching Auvik tenants from:', url);

      const response = await this.makeApiCall<AuvikTenantResponse>(url);

      const tenants = response.data.map((item) => ({
        id: item.id,
        domainPrefix: item.attributes.domainPrefix,
        tenantType: item.attributes.tenantType as 'multiClient' | 'client',
        parentId: item.relationships?.parent?.data?.id,
      }));

      console.log(`Fetched ${tenants.length} Auvik tenants`);
      return tenants;
    } catch (error) {
      console.error('Failed to fetch Auvik tenants:', error);
      return [];
    }
  }

  /**
   * Get all devices (requires tenant filtering)
   */
  async getAllDevices(): Promise<AuvikDevice[]> {
    try {
      // Fetch all tenants first
      const tenants = await this.getTenants();
      if (tenants.length === 0) {
        console.warn('No Auvik tenants found');
        return [];
      }

      // Fetch devices for all tenants
      const allDevices: AuvikDevice[] = [];
      for (const tenant of tenants) {
        const devices = await this.getDevicesByTenant(tenant.id);
        allDevices.push(...devices);
      }

      console.log(`Fetched total of ${allDevices.length} Auvik devices across all tenants`);
      return allDevices;
    } catch (error) {
      console.error('Failed to fetch all Auvik devices:', error);
      return [];
    }
  }

  /**
   * Get devices filtered by tenant ID
   */
  async getDevicesByTenant(tenantId: string): Promise<AuvikDevice[]> {
    try {
      const allDevices: AuvikDevice[] = [];
      let nextUrl: string | null = `${this.config.apiUrl}/v1/inventory/device/info?tenants=${tenantId}&page[first]=100`;
      
      console.log(`Fetching Auvik devices for tenant ${tenantId}`);

      // Paginate through all results
      while (nextUrl) {
        const response: AuvikDeviceResponse = await this.makeApiCall<AuvikDeviceResponse>(nextUrl);
        
        const devices = response.data.map((item) => this.transformDevice(item, tenantId));
        allDevices.push(...devices);
        
        // Check if there's a next page
        nextUrl = response.links?.next || null;
        
        if (nextUrl) {
          console.log(`Fetching next page for tenant ${tenantId} (${allDevices.length} devices so far)`);
        }
      }

      console.log(`Fetched total of ${allDevices.length} devices for tenant ${tenantId}`);
      return allDevices;
    } catch (error) {
      console.error(`Failed to fetch devices for tenant ${tenantId}:`, error);
      return [];
    }
  }

  /**
   * Transform Auvik API device response to AuvikDevice interface
   */
  private transformDevice(item: AuvikDeviceResponse['data'][0], tenantId: string): AuvikDevice {
    return {
      id: item.id,
      deviceName: item.attributes.deviceName,
      serialNumber: item.attributes.serialNumber,
      macAddresses: [], // MAC addresses would need to be fetched from device details
      ipAddresses: item.attributes.ipAddresses || [],
      deviceType: item.attributes.deviceType,
      manufacturer: item.attributes.vendorName,
      model: item.attributes.makeModel,
      makeModel: item.attributes.makeModel,
      vendorName: item.attributes.vendorName,
      firmwareVersion: item.attributes.firmwareVersion,
      softwareVersion: item.attributes.softwareVersion,
      onlineStatus: this.normalizeOnlineStatus(item.attributes.onlineStatus),
      lastSeenTime: item.attributes.lastSeenTime,
      uptime: undefined, // Would need to be calculated from lastSeenTime
      tenantId: tenantId,
      tenantName: item.relationships?.tenant?.data?.attributes?.domainPrefix,
      description: item.attributes.description,
    };
  }

  /**
   * Normalize online status to expected values
   */
  private normalizeOnlineStatus(status: string): 'online' | 'offline' | 'unknown' {
    const normalized = status.toLowerCase();
    if (normalized === 'online') return 'online';
    if (normalized === 'offline') return 'offline';
    return 'unknown';
  }

  /**
   * Find tenant by company ID using database mapping
   */
  async findTenantByCompanyId(companyId: number): Promise<AuvikTenant | null> {
    try {
      // Query database directly for mapping
      const { Pool } = require('pg');
      const pool = new Pool({
        host: process.env.POSTGRES_HOST,
        port: parseInt(process.env.POSTGRES_PORT || '5432'),
        database: process.env.POSTGRES_DB,
        user: process.env.POSTGRES_USER,
        password: process.env.POSTGRES_PASSWORD,
      });

      const result = await pool.query(
        'SELECT auvik_tenant_id FROM auvik_tenant_mappings WHERE autotask_company_id = $1',
        [companyId]
      );

      await pool.end();

      if (result.rows.length > 0) {
        const auvikTenantId = result.rows[0].auvik_tenant_id;
        const tenants = await this.getTenants();
        const tenant = tenants.find(t => t.id === auvikTenantId);
        
        if (tenant) {
          console.log(`Found tenant via mapping: ${tenant.domainPrefix} for company ID: ${companyId}`);
          return tenant;
        }
      }
      
      return null;
    } catch (error) {
      console.error('Failed to find tenant by company ID:', error);
      return null;
    }
  }

  /**
   * Normalize company name for matching by removing common suffixes and special characters
   */
  private normalizeCompanyName(name: string): string {
    return name
      .toLowerCase()
      .trim()
      // Remove common legal suffixes
      .replace(/,?\s*(inc\.?|llc\.?|ltd\.?|corp\.?|corporation|company|co\.?|limited|l\.?l\.?c\.?|incorporated)$/i, '')
      // Remove commas and other punctuation
      .replace(/[,\.]/g, '')
      // Replace multiple spaces with single space
      .replace(/\s+/g, ' ')
      .trim();
  }

  /**
   * Find tenant by name (case-insensitive, fuzzy match)
   * This is a fallback when no mapping exists
   */
  async findTenantByName(companyName: string): Promise<AuvikTenant | null> {
    try {
      const tenants = await this.getTenants();
      const normalizedCompanyName = this.normalizeCompanyName(companyName);

      console.log(`Searching for Auvik tenant matching company: "${companyName}"`);
      console.log(`Normalized company name: "${normalizedCompanyName}"`);
      console.log(`Available tenants: ${tenants.map(t => t.domainPrefix).join(', ')}`);

      // Try exact match first (normalized)
      let match = tenants.find(
        (t) => this.normalizeCompanyName(t.domainPrefix) === normalizedCompanyName
      );

      if (match) {
        console.log(`Found exact tenant match: ${match.domainPrefix} for company: ${companyName}`);
        return match;
      }

      // Try exact match on original (case-insensitive)
      match = tenants.find(
        (t) => t.domainPrefix.toLowerCase() === companyName.toLowerCase().trim()
      );

      if (match) {
        console.log(`Found exact tenant match (original): ${match.domainPrefix} for company: ${companyName}`);
        return match;
      }

      // Try fuzzy match (contains) with normalized names
      // Find all potential matches and pick the best one (longest match)
      const potentialMatches = tenants.filter((t) => {
        const normalizedTenant = this.normalizeCompanyName(t.domainPrefix);
        return normalizedTenant.includes(normalizedCompanyName) ||
               normalizedCompanyName.includes(normalizedTenant);
      });

      if (potentialMatches.length > 0) {
        // Sort by length of normalized tenant name (descending) to prefer more specific matches
        match = potentialMatches.sort((a, b) => {
          const aNorm = this.normalizeCompanyName(a.domainPrefix);
          const bNorm = this.normalizeCompanyName(b.domainPrefix);
          return bNorm.length - aNorm.length;
        })[0];

        console.log(`Found fuzzy tenant match: ${match.domainPrefix} for company: ${companyName}`);
        if (potentialMatches.length > 1) {
          console.log(`Other potential matches: ${potentialMatches.slice(1).map(t => t.domainPrefix).join(', ')}`);
        }
        return match;
      }

      console.log(`No tenant match found for company: ${companyName}`);
      console.log(`Tried to match "${normalizedCompanyName}" against: ${tenants.map(t => this.normalizeCompanyName(t.domainPrefix)).join(', ')}`);
      return null;
    } catch (error) {
      console.error('Failed to find tenant by name:', error);
      return null;
    }
  }
}
