import * as plugins from './cloudflare.plugins.js';
import * as interfaces from './interfaces/index.js';
import { WorkerManager } from './cloudflare.classes.workermanager.js';
import { logger } from './cloudflare.logger.js';

export interface IWorkerRoute extends interfaces.ICflareWorkerRoute {
  zoneName: string;
}

export interface IWorkerRouteDefinition {
  zoneName: string;
  pattern: string;
}

export class CloudflareWorker {
  // STATIC
  public static async fromApiObject(
    workerManager: WorkerManager,
    apiObject
  ): Promise<CloudflareWorker> {
    const newWorker = new CloudflareWorker(workerManager);
    Object.assign(newWorker, apiObject);
    await newWorker.getRoutes();
    return newWorker;
  }

  // INSTANCE
  private workerManager: WorkerManager;

  public script: string;
  public id: string;
  public etag: string;
  // tslint:disable-next-line: variable-name
  public created_on: string;
  // tslint:disable-next-line: variable-name
  public modified_on: string;

  public routes: IWorkerRoute[] = [];
  constructor(workerManagerArg: WorkerManager) {
    this.workerManager = workerManagerArg;
  }

  /**
   * gets all routes for a worker
   */
  public async getRoutes() {
    try {
      this.routes = []; // Reset routes before fetching
      
      // Get all zones using the async iterator
      const zones: plugins.ICloudflareTypes['Zone'][] = [];
      for await (const zone of this.workerManager.cfAccount.apiAccount.zones.list()) {
        zones.push(zone);
      }
      
      if (zones.length === 0) {
        logger.log('warn', 'No zones found for the account');
        return;
      }
      
      for (const zone of zones) {
        try {
          if (!zone || !zone.id) {
            logger.log('warn', 'Zone is missing ID property');
            continue;
          }
          
          // Get worker routes for this zone
          const apiRoutes = [];
          for await (const route of this.workerManager.cfAccount.apiAccount.workers.routes.list({
            zone_id: zone.id
          })) {
            apiRoutes.push(route);
          }
          
          // Filter for routes that match this worker's ID
          for (const route of apiRoutes) {
            if (route.script === this.id) {
              logger.log('debug', `Found route for worker ${this.id}: ${route.pattern}`);
              this.routes.push({ ...route, zoneName: zone.name });
            }
          }
        } catch (error) {
          logger.log('error', `Failed to get worker routes for zone ${zone.name || zone.id}: ${error.message}`);
        }
      }
      
      logger.log('info', `Found ${this.routes.length} routes for worker ${this.id}`);
    } catch (error) {
      logger.log('error', `Failed to get routes for worker ${this.id}: ${error.message}`);
      // Initialize routes as empty array in case of error
      this.routes = [];
    }
  }

  /**
   * Sets routes for this worker
   * @param routeArray Array of route definitions
   */
  public async setRoutes(routeArray: IWorkerRouteDefinition[]) {
    // First get all existing routes to determine what we need to create/update
    await this.getRoutes();
    
    for (const newRoute of routeArray) {
      // Determine whether a route is new, needs an update, or is already up to date
      let routeStatus: 'new' | 'needsUpdate' | 'alreadyUpToDate' = 'new';
      let existingRouteId: string;
      
      for (const existingRoute of this.routes) {
        if (existingRoute.pattern === newRoute.pattern) {
          routeStatus = 'needsUpdate';
          existingRouteId = existingRoute.id;
          
          if (existingRoute.script === this.id) {
            routeStatus = 'alreadyUpToDate';
            logger.log('info', `Route ${newRoute.pattern} already exists, no update needed`);
          }
        }
      }
      
      try {
        // Get the zone ID
        const zone = await this.workerManager.cfAccount.zoneManager.getZoneByName(newRoute.zoneName);
        
        if (!zone) {
          logger.log('error', `Zone ${newRoute.zoneName} not found`);
          continue;
        }
        
        // Handle route creation, update, or skip if already up to date
        if (routeStatus === 'new') {
          await this.workerManager.cfAccount.apiAccount.workers.routes.create({
            zone_id: zone.id,
            pattern: newRoute.pattern,
            script: this.id
          });
          
          logger.log('info', `Created new route ${newRoute.pattern} for worker ${this.id}`);
        } else if (routeStatus === 'needsUpdate') {
          await this.workerManager.cfAccount.apiAccount.workers.routes.update(existingRouteId, {
            zone_id: zone.id,
            pattern: newRoute.pattern,
            script: this.id
          });
          
          logger.log('info', `Updated route ${newRoute.pattern} for worker ${this.id}`);
        }
      } catch (error) {
        logger.log('error', `Failed to set route ${newRoute.pattern}: ${error.message}`);
      }
    }
    
    // Refresh routes after all changes
    await this.getRoutes();
  }
  
  /**
   * Upload or update worker script content
   * @param scriptContent The worker script content
   * @returns Updated worker object
   */
  public async updateScript(scriptContent: string): Promise<CloudflareWorker> {
    if (!this.workerManager.cfAccount.preselectedAccountId) {
      throw new Error('No account selected. Please select it first on the account.');
    }
    
    try {
      logger.log('info', `Updating script for worker ${this.id}`);
      
      // Use the official client to update the script (upload new content)
      // Build params as any to include the script form part without TS errors
      const updateParams: any = {
        account_id: this.workerManager.cfAccount.preselectedAccountId,
        metadata: { body_part: 'script' },
      };
      updateParams['CF-WORKER-BODY-PART'] = 'script';
      updateParams['script'] = scriptContent;
      const updatedWorker = await this.workerManager.cfAccount.apiAccount.workers.scripts.content.update(this.id, updateParams);
      
      // Update this instance with new data
      if (updatedWorker && typeof updatedWorker === 'object') {
        Object.assign(this, updatedWorker);
      }
      
      // Always ensure the script property is updated
      this.script = scriptContent;
      
      return this;
    } catch (error) {
      logger.log('error', `Failed to update worker script: ${error.message}`);
      throw error;
    }
  }
  
  /**
   * Delete this worker script
   * @returns True if deletion was successful
   */
  public async delete(): Promise<boolean> {
    if (!this.workerManager.cfAccount.preselectedAccountId) {
      throw new Error('No account selected. Please select it first on the account.');
    }
    
    try {
      logger.log('info', `Deleting worker ${this.id}`);
      
      // Use the official client to delete the worker
      await this.workerManager.cfAccount.apiAccount.workers.scripts.delete(this.id, {
        account_id: this.workerManager.cfAccount.preselectedAccountId
      });
      
      return true;
    } catch (error) {
      logger.log('error', `Failed to delete worker: ${error.message}`);
      return false;
    }
  }
}