/**
 * Resource pool implementation for bulkhead pattern
 */

import {
  BulkheadConfig,
  ResourcePoolMetrics,
  ResilienceResult,
  ResilienceEvent,
  MarineEnvironmentStatus
} from '../types';

import {
  OperationalContext,
  OperationalContextType
} from '../types/marine-constants';

/**
 * Resource request with priority and context
 */
export interface ResourceRequest {
  id: string;
  priority: 'critical' | 'normal' | 'low';
  operationalContext: OperationalContextType;
  requestedAt: Date;
  timeoutMs: number;
  marineSystemType: 'navigation' | 'safety' | 'comfort' | 'maintenance';
}

/**
 * Resource allocation result
 */
export interface ResourceAllocation {
  granted: boolean;
  resourceId?: string;
  waitTime: number;
  queuePosition?: number;
  estimatedWaitTime?: number;
  reason?: string;
}

/**
 * Active resource usage tracking
 */
interface ActiveResource {
  resourceId: string;
  request: ResourceRequest;
  allocatedAt: Date;
  powerConsumption: number;
  systemHealthImpact: number;
}

/**
 * Resource pool for marine bulkhead pattern
 */
export class ResourcePool {
  private config: BulkheadConfig;
  private metrics: ResourcePoolMetrics;
  private activeResources: Map<string, ActiveResource> = new Map();
  private requestQueue: ResourceRequest[] = [];
  private eventListeners: ((event: ResilienceEvent) => void)[] = [];
  private marineEnvironment?: MarineEnvironmentStatus;
  private nextResourceId = 1;

  constructor(config: BulkheadConfig) {
    this.config = { ...config };
    this.metrics = this.initializeMetrics();
  }

  /**
   * Request a resource from the pool
   */
  async requestResource(
    request: Omit<ResourceRequest, 'id' | 'requestedAt'>
  ): Promise<ResourceAllocation> {
    const fullRequest: ResourceRequest = {
      ...request,
      id: `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
      requestedAt: new Date()
    };

    const startTime = Date.now();

    // Check if resource can be allocated immediately
    if (this.canAllocateImmediately(fullRequest)) {
      const allocation = this.allocateResource(fullRequest);
      const waitTime = Date.now() - startTime;
      
      this.updateMetrics(waitTime, false);
      return {
        granted: true,
        resourceId: allocation.resourceId,
        waitTime,
        reason: 'Immediate allocation'
      };
    }

    // Check if request should be queued or rejected
    if (!this.shouldQueue(fullRequest)) {
      this.metrics.rejectedRequests++;
      this.emitEvent('bulkhead_rejection', fullRequest.id, {
        reason: 'Pool capacity exceeded',
        poolName: this.config.poolName,
        priority: fullRequest.priority,
        marineSystemType: fullRequest.marineSystemType
      }, 'warning');

      return {
        granted: false,
        waitTime: Date.now() - startTime,
        reason: 'Pool capacity exceeded - request rejected'
      };
    }

    // Queue the request
    return this.queueRequest(fullRequest);
  }

  /**
   * Release a resource back to the pool
   */
  releaseResource(resourceId: string): boolean {
    const activeResource = this.activeResources.get(resourceId);
    if (!activeResource) {
      return false;
    }

    // Remove from active resources
    this.activeResources.delete(resourceId);
    this.metrics.activeRequests--;

    // Update metrics
    const usageTime = Date.now() - activeResource.allocatedAt.getTime();
    this.updateResourceMetrics(activeResource, usageTime);

    // Process next request in queue if any
    this.processQueue();

    this.emitEvent('recovery', resourceId, {
      poolName: this.config.poolName,
      usageTime,
      marineSystemType: activeResource.request.marineSystemType
    }, 'info');

    return true;
  }

  /**
   * Update marine environment for adaptive behavior
   */
  updateMarineEnvironment(environment: MarineEnvironmentStatus): void {
    this.marineEnvironment = environment;
    this.adjustPoolCapacity();
  }

  /**
   * Get current pool metrics
   */
  getMetrics(): ResourcePoolMetrics {
    return { ...this.metrics };
  }

  /**
   * Get current pool status
   */
  getStatus() {
    return {
      config: this.config,
      metrics: this.metrics,
      activeResources: Array.from(this.activeResources.values()).map(r => ({
        resourceId: r.resourceId,
        priority: r.request.priority,
        marineSystemType: r.request.marineSystemType,
        allocatedAt: r.allocatedAt,
        powerConsumption: r.powerConsumption
      })),
      queuedRequests: this.requestQueue.map(r => ({
        id: r.id,
        priority: r.priority,
        marineSystemType: r.marineSystemType,
        waitTime: Date.now() - r.requestedAt.getTime()
      }))
    };
  }

  /**
   * Add event listener
   */
  onEvent(listener: (event: ResilienceEvent) => void): void {
    this.eventListeners.push(listener);
  }

  /**
   * Force release all resources (emergency/maintenance)
   */
  forceReleaseAll(reason: string = 'Emergency release'): void {
    const releasedCount = this.activeResources.size;
    
    this.activeResources.clear();
    this.requestQueue = [];
    this.metrics.activeRequests = 0;
    this.metrics.queuedRequests = 0;

    this.emitEvent('recovery', 'pool-reset', {
      poolName: this.config.poolName,
      releasedCount,
      reason
    }, 'warning');
  }

  /**
   * Check if resource can be allocated immediately
   */
  private canAllocateImmediately(request: ResourceRequest): boolean {
    // Check basic capacity
    if (this.metrics.activeRequests >= this.config.maxConcurrentRequests) {
      return false;
    }

    // Check marine-specific constraints
    if (this.marineEnvironment?.criticalOperationsOnly) {
      return request.priority === 'critical' || 
             request.marineSystemType === 'safety' ||
             request.marineSystemType === 'navigation';
    }

    // Check power constraints
    if (this.marineEnvironment?.powerStatus === 'critical') {
      const powerConsumption = this.estimatePowerConsumption(request);
      const currentPowerUsage = this.metrics.powerImpact;
      
      if (currentPowerUsage + powerConsumption > this.getMaxPowerAllowance()) {
        return false;
      }
    }

    return true;
  }

  /**
   * Allocate a resource
   */
  private allocateResource(request: ResourceRequest): { resourceId: string } {
    const resourceId = `${this.config.poolName}-${this.nextResourceId++}`;
    
    const activeResource: ActiveResource = {
      resourceId,
      request,
      allocatedAt: new Date(),
      powerConsumption: this.estimatePowerConsumption(request),
      systemHealthImpact: this.estimateSystemHealthImpact(request)
    };

    this.activeResources.set(resourceId, activeResource);
    this.metrics.activeRequests++;
    this.metrics.powerImpact += activeResource.powerConsumption;
    this.metrics.systemHealthImpact = Math.max(
      this.metrics.systemHealthImpact,
      activeResource.systemHealthImpact
    );

    if (request.priority === 'critical') {
      this.metrics.criticalRequestsActive++;
    }

    return { resourceId };
  }

  /**
   * Check if request should be queued
   */
  private shouldQueue(request: ResourceRequest): boolean {
    // Always queue critical requests
    if (request.priority === 'critical') {
      return true;
    }

    // Check queue capacity
    const maxQueueSize = Math.floor(this.config.maxConcurrentRequests * 2);
    if (this.requestQueue.length >= maxQueueSize) {
      return false;
    }

    // Check wait time limits
    if (request.timeoutMs < this.config.maxWaitTime) {
      return false;
    }

    return true;
  }

  /**
   * Queue a request
   */
  private async queueRequest(request: ResourceRequest): Promise<ResourceAllocation> {
    // Insert request in priority order
    this.insertRequestByPriority(request);
    this.metrics.queuedRequests++;

    const queuePosition = this.requestQueue.findIndex(r => r.id === request.id) + 1;
    const estimatedWaitTime = this.estimateWaitTime(queuePosition);

    // Wait for allocation or timeout
    return new Promise((resolve) => {
      const startTime = Date.now();
      const timeoutId = setTimeout(() => {
        // Remove from queue if timed out
        this.removeFromQueue(request.id);
        resolve({
          granted: false,
          waitTime: Date.now() - startTime,
          reason: 'Request timed out in queue'
        });
      }, request.timeoutMs);

      // Store timeout ID for cleanup
      (request as any).timeoutId = timeoutId;
      (request as any).resolve = (allocation: ResourceAllocation) => {
        clearTimeout(timeoutId);
        resolve(allocation);
      };
    });
  }

  /**
   * Process queued requests
   */
  private processQueue(): void {
    while (this.requestQueue.length > 0) {
      const nextRequest = this.requestQueue[0];
      if (!nextRequest) break;
      
      if (this.canAllocateImmediately(nextRequest)) {
        // Remove from queue
        this.requestQueue.shift();
        this.metrics.queuedRequests--;

        // Allocate resource
        const allocation = this.allocateResource(nextRequest);
        const waitTime = Date.now() - nextRequest.requestedAt.getTime();
        
        this.updateMetrics(waitTime, true);

        // Resolve the promise
        if ((nextRequest as any).resolve) {
          (nextRequest as any).resolve({
            granted: true,
            resourceId: allocation.resourceId,
            waitTime,
            reason: 'Allocated from queue'
          });
        }
      } else {
        // Can't allocate more resources
        break;
      }
    }
  }

  /**
   * Insert request by priority
   */
  private insertRequestByPriority(request: ResourceRequest): void {
    const priorityOrder = { critical: 0, normal: 1, low: 2 };
    const requestPriority = priorityOrder[request.priority];

    let insertIndex = this.requestQueue.length;
    for (let i = 0; i < this.requestQueue.length; i++) {
      const queuedPriority = priorityOrder[this.requestQueue[i]!.priority];
      if (requestPriority < queuedPriority) {
        insertIndex = i;
        break;
      }
    }

    this.requestQueue.splice(insertIndex, 0, request);
  }

  /**
   * Remove request from queue
   */
  private removeFromQueue(requestId: string): boolean {
    const index = this.requestQueue.findIndex(r => r.id === requestId);
    if (index >= 0) {
      this.requestQueue.splice(index, 1);
      this.metrics.queuedRequests--;
      this.metrics.rejectedRequests++;
      return true;
    }
    return false;
  }

  /**
   * Estimate power consumption for request
   */
  private estimatePowerConsumption(request: ResourceRequest): number {
    const basePower = {
      navigation: 5,    // Watts
      safety: 3,
      comfort: 10,
      maintenance: 15
    };

    let power = basePower[request.marineSystemType] || 5;

    // Adjust for priority
    if (request.priority === 'critical') {
      power *= 1.5; // Critical operations may use more power
    }

    return power;
  }

  /**
   * Estimate system health impact
   */
  private estimateSystemHealthImpact(request: ResourceRequest): number {
    const baseImpact = {
      navigation: 0.1,
      safety: 0.05,
      comfort: 0.3,
      maintenance: 0.5
    };

    return baseImpact[request.marineSystemType] || 0.1;
  }

  /**
   * Get maximum power allowance based on marine environment
   */
  private getMaxPowerAllowance(): number {
    if (!this.marineEnvironment) {
      return 100; // Default 100W
    }

    switch (this.marineEnvironment.powerStatus) {
      case 'critical':
        return 30; // Minimal power usage
      case 'conservation':
        return 60; // Reduced power usage
      case 'normal':
      default:
        return 100; // Normal power usage
    }
  }

  /**
   * Estimate wait time for queue position
   */
  private estimateWaitTime(queuePosition: number): number {
    if (this.metrics.averageWaitTime === 0) {
      return queuePosition * 5000; // Default 5 seconds per position
    }

    return queuePosition * this.metrics.averageWaitTime;
  }

  /**
   * Adjust pool capacity based on marine conditions
   */
  private adjustPoolCapacity(): void {
    if (!this.marineEnvironment) return;

    // Reduce capacity in critical conditions
    if (this.marineEnvironment.criticalOperationsOnly) {
      this.config.maxConcurrentRequests = Math.max(
        Math.floor(this.config.maxConcurrentRequests * 0.5),
        2 // Minimum 2 resources
      );
    }

    // Adjust wait times based on conditions
    if (this.marineEnvironment.seaState === 'very_rough') {
      this.config.maxWaitTime *= 2; // Allow longer waits in rough seas
    }
  }

  /**
   * Update metrics
   */
  private updateMetrics(waitTime: number, fromQueue: boolean): void {
    // Update wait time metrics
    const totalWaitTime = this.metrics.averageWaitTime * this.metrics.totalCapacity;
    this.metrics.averageWaitTime = (totalWaitTime + waitTime) / (this.metrics.totalCapacity + 1);
    this.metrics.maxWaitTime = Math.max(this.metrics.maxWaitTime, waitTime);
  }

  /**
   * Update resource-specific metrics
   */
  private updateResourceMetrics(resource: ActiveResource, usageTime: number): void {
    this.metrics.powerImpact -= resource.powerConsumption;
    
    if (resource.request.priority === 'critical') {
      this.metrics.criticalRequestsActive--;
    }

    // Recalculate system health impact
    this.metrics.systemHealthImpact = Math.max(
      ...Array.from(this.activeResources.values()).map(r => r.systemHealthImpact),
      0
    );
  }

  /**
   * Emit resilience event
   */
  private emitEvent(
    eventType: ResilienceEvent['eventType'],
    component: string,
    details: Record<string, any>,
    severity: ResilienceEvent['severity']
  ): void {
    const event: ResilienceEvent = {
      timestamp: new Date(),
      eventType,
      component,
      details,
      severity,
      marineContext: {
        operationalContext: this.marineEnvironment?.powerStatus === 'critical' ? 
          OperationalContext.EMERGENCY : OperationalContext.SAILING,
        environmentalImpact: this.marineEnvironment?.seaState === 'very_rough',
        safetyImpact: details['marineSystemType'] === 'safety'
      }
    };

    this.eventListeners.forEach(listener => {
      try {
        listener(event);
      } catch (error) {
        console.error('Error in resource pool event listener:', error);
      }
    });
  }

  /**
   * Initialize metrics
   */
  private initializeMetrics(): ResourcePoolMetrics {
    return {
      poolName: this.config.poolName,
      totalCapacity: this.config.maxConcurrentRequests,
      availableResources: this.config.maxConcurrentRequests,
      activeRequests: 0,
      queuedRequests: 0,
      rejectedRequests: 0,
      averageWaitTime: 0,
      maxWaitTime: 0,
      powerImpact: 0,
      criticalRequestsActive: 0,
      systemHealthImpact: 0
    };
  }
}
