// IP Address Management (IPAM) Service
// Manages VMID and network allocation for IRU deployments

import prisma from '@/shared/database/prisma';
import { logger } from '@/infrastructure/monitoring/logger';
import { DbisError, ErrorCode } from '@/shared/types';

export interface NetworkAllocation {
  vmid: number;
  ipAddress: string;
  gateway: string;
  subnet: string;
  vlan?: number;
  networkId: string;
}

export interface IPAMPool {
  id: string;
  name: string;
  subnet: string;
  gateway: string;
  startRange: string;
  endRange: string;
  vlan?: number;
  availableIPs: string[];
  allocatedIPs: Map<string, NetworkAllocation>;
}

export class IPAMService {
  private pools: Map<string, IPAMPool> = new Map();
  private vmidCounter: number = 20000; // Starting VMID

  constructor() {
    this.initializePools();
  }

  /**
   * Initialize IPAM pools from database or configuration
   */
  private async initializePools(): Promise<void> {
    try {
      // Load pools from database if they exist
      const dbPools = await prisma.iruIPAMPool.findMany({
        where: { status: 'active' },
      });

      for (const pool of dbPools) {
        this.pools.set(pool.poolId, {
          id: pool.poolId,
          name: pool.name,
          subnet: pool.subnet,
          gateway: pool.gateway,
          startRange: pool.startRange,
          endRange: pool.endRange,
          vlan: pool.vlan || undefined,
          availableIPs: this.generateIPRange(pool.startRange, pool.endRange),
          allocatedIPs: new Map(),
        });
      }

      // If no pools in database, create default pool
      if (this.pools.size === 0) {
        await this.createDefaultPool();
      }
    } catch (error) {
      logger.warn('Failed to load IPAM pools from database, using defaults', {
        error: error instanceof Error ? error.message : 'Unknown error',
      });
      await this.createDefaultPool();
    }
  }

  /**
   * Create default IPAM pool
   */
  private async createDefaultPool(): Promise<void> {
    const defaultPool: IPAMPool = {
      id: 'default',
      name: 'Default IRU Network Pool',
      subnet: '10.100.0.0/24',
      gateway: '10.100.0.1',
      startRange: '10.100.0.10',
      endRange: '10.100.0.250',
      availableIPs: this.generateIPRange('10.100.0.10', '10.100.0.250'),
      allocatedIPs: new Map(),
    };

    this.pools.set('default', defaultPool);

    // Save to database
    try {
      await prisma.iruIPAMPool.create({
        data: {
          id: `pool-${Date.now()}`,
          poolId: 'default',
          name: defaultPool.name,
          subnet: defaultPool.subnet,
          gateway: defaultPool.gateway,
          startRange: defaultPool.startRange,
          endRange: defaultPool.endRange,
          status: 'active',
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      });
    } catch (error) {
      logger.warn('Failed to save default pool to database', {
        error: error instanceof Error ? error.message : 'Unknown error',
      });
    }
  }

  /**
   * Allocate network resources for deployment
   */
  async allocateNetwork(
    subscriptionId: string,
    containerCount: number,
    poolId: string = 'default'
  ): Promise<NetworkAllocation[]> {
    const pool = this.pools.get(poolId);
    if (!pool) {
      throw new DbisError(ErrorCode.NOT_FOUND, `IPAM pool ${poolId} not found`);
    }

    if (pool.availableIPs.length < containerCount) {
      throw new DbisError(
        ErrorCode.RESOURCE_EXHAUSTED,
        `Insufficient IP addresses in pool ${poolId}. Available: ${pool.availableIPs.length}, Required: ${containerCount}`
      );
    }

    const allocations: NetworkAllocation[] = [];

    for (let i = 0; i < containerCount; i++) {
      const vmid = await this.allocateVMID();
      const ipAddress = pool.availableIPs.shift()!;

      const allocation: NetworkAllocation = {
        vmid,
        ipAddress,
        gateway: pool.gateway,
        subnet: pool.subnet,
        vlan: pool.vlan,
        networkId: `${subscriptionId}-${vmid}`,
      };

      pool.allocatedIPs.set(ipAddress, allocation);
      allocations.push(allocation);

      // Record allocation in database
      await prisma.iruNetworkAllocation.create({
        data: {
          id: `alloc-${Date.now()}-${i}`,
          allocationId: allocation.networkId,
          subscriptionId,
          poolId: pool.id,
          vmid,
          ipAddress,
          gateway: pool.gateway,
          subnet: pool.subnet,
          vlan: pool.vlan,
          status: 'allocated',
          allocatedAt: new Date(),
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      });
    }

    logger.info('Network resources allocated', {
      subscriptionId,
      poolId,
      containerCount,
      allocations: allocations.length,
    });

    return allocations;
  }

  /**
   * Release network resources
   */
  async releaseNetwork(subscriptionId: string, allocations: NetworkAllocation[]): Promise<void> {
    for (const allocation of allocations) {
      // Find pool containing this IP
      for (const pool of this.pools.values()) {
        if (pool.allocatedIPs.has(allocation.ipAddress)) {
          pool.allocatedIPs.delete(allocation.ipAddress);
          pool.availableIPs.push(allocation.ipAddress);
          break;
        }
      }

      // Update database
      await prisma.iruNetworkAllocation.updateMany({
        where: {
          subscriptionId,
          ipAddress: allocation.ipAddress,
        },
        data: {
          status: 'released',
          releasedAt: new Date(),
          updatedAt: new Date(),
        },
      });
    }

    logger.info('Network resources released', {
      subscriptionId,
      count: allocations.length,
    });
  }

  /**
   * Allocate VMID
   */
  private async allocateVMID(): Promise<number> {
    // Check for available VMID in database
    const lastAllocation = await prisma.iruNetworkAllocation.findFirst({
      orderBy: { vmid: 'desc' },
      select: { vmid: true },
    });

    if (lastAllocation && lastAllocation.vmid >= this.vmidCounter) {
      this.vmidCounter = lastAllocation.vmid + 1;
    }

    const vmid = this.vmidCounter++;
    return vmid;
  }

  /**
   * Generate IP range
   */
  private generateIPRange(start: string, end: string): string[] {
    const startParts = start.split('.').map(Number);
    const endParts = end.split('.').map(Number);
    const ips: string[] = [];

    for (let a = startParts[0]; a <= endParts[0]; a++) {
      for (let b = startParts[1]; b <= endParts[1]; b++) {
        for (let c = startParts[2]; c <= endParts[2]; c++) {
          const startD = a === startParts[0] && b === startParts[1] && c === startParts[2] ? startParts[3] : 0;
          const endD = a === endParts[0] && b === endParts[1] && c === endParts[2] ? endParts[3] : 255;

          for (let d = startD; d <= endD; d++) {
            ips.push(`${a}.${b}.${c}.${d}`);
          }
        }
      }
    }

    return ips;
  }

  /**
   * Get pool statistics
   */
  async getPoolStats(poolId: string): Promise<{
    total: number;
    allocated: number;
    available: number;
    utilization: number;
  }> {
    const pool = this.pools.get(poolId);
    if (!pool) {
      throw new DbisError(ErrorCode.NOT_FOUND, `IPAM pool ${poolId} not found`);
    }

    const total = pool.availableIPs.length + pool.allocatedIPs.size;
    const allocated = pool.allocatedIPs.size;
    const available = pool.availableIPs.length;
    const utilization = total > 0 ? (allocated / total) * 100 : 0;

    return {
      total,
      allocated,
      available,
      utilization,
    };
  }
}

export const ipamService = new IPAMService();
