import * as plugins from '../plugins.js';
import { logger } from '../logger.js';
import { VlanManager, type IMacVlanMapping, type IVlanManagerConfig } from './classes.vlan.manager.js';
import { AccountingManager, type IAccountingSession, type IAccountingManagerConfig } from './classes.accounting.manager.js';

/**
 * RADIUS client (NAS) configuration
 */
export interface IRadiusClient {
  /** Client name for identification */
  name: string;
  /** IP address or CIDR range */
  ipRange: string;
  /** Shared secret for this client */
  secret: string;
  /** Optional description */
  description?: string;
  /** Whether this client is enabled */
  enabled: boolean;
}

/**
 * RADIUS server configuration
 */
export interface IRadiusServerConfig {
  /** Authentication port (default: 1812) */
  authPort?: number;
  /** Accounting port (default: 1813) */
  acctPort?: number;
  /** Bind address (default: 0.0.0.0) */
  bindAddress?: string;
  /** NAS clients configuration */
  clients: IRadiusClient[];
  /** VLAN assignment configuration */
  vlanAssignment?: IVlanManagerConfig & {
    /** Static MAC to VLAN mappings */
    mappings?: Array<Omit<IMacVlanMapping, 'createdAt' | 'updatedAt'>>;
  };
  /** Accounting configuration */
  accounting?: IAccountingManagerConfig & {
    /** Whether accounting is enabled */
    enabled: boolean;
  };
}

/**
 * RADIUS authentication result
 */
export interface IRadiusAuthResult {
  /** Whether authentication was successful */
  success: boolean;
  /** Reject reason (if not successful) */
  rejectReason?: string;
  /** Reply message to send to client */
  replyMessage?: string;
  /** Session timeout in seconds */
  sessionTimeout?: number;
  /** Idle timeout in seconds */
  idleTimeout?: number;
  /** VLAN to assign */
  vlanId?: number;
  /** Framed IP address to assign */
  framedIpAddress?: string;
}

/**
 * Authentication request data from RADIUS
 */
export interface IAuthRequestData {
  username: string;
  password?: string;
  nasIpAddress: string;
  nasPort?: number;
  nasPortType?: string;
  nasIdentifier?: string;
  calledStationId?: string;
  callingStationId?: string;
  serviceType?: string;
  framedMtu?: number;
}

/**
 * RADIUS Server wrapper that provides:
 * - MAC Authentication Bypass (MAB) for network devices
 * - VLAN assignment based on MAC address
 * - Accounting for session tracking and billing
 * - Integration with SmartProxy routing
 */
export class RadiusServer {
  private radiusServer?: plugins.smartradius.RadiusServer;
  private vlanManager: VlanManager;
  private accountingManager: AccountingManager;
  private config: IRadiusServerConfig;
  private running: boolean = false;

  // Statistics
  private stats = {
    authRequests: 0,
    authAccepts: 0,
    authRejects: 0,
    accountingRequests: 0,
    startTime: 0,
  };

  constructor(config: IRadiusServerConfig) {
    this.config = {
      authPort: config.authPort ?? 1812,
      acctPort: config.acctPort ?? 1813,
      bindAddress: config.bindAddress ?? '0.0.0.0',
      ...config,
    };

    // Initialize VLAN manager
    this.vlanManager = new VlanManager(config.vlanAssignment);

    // Initialize accounting manager
    this.accountingManager = new AccountingManager(config.accounting);
  }

  /**
   * Start the RADIUS server
   */
  async start(): Promise<void> {
    if (this.running) {
      logger.log('warn', 'RADIUS server is already running');
      return;
    }

    logger.log('info', `Starting RADIUS server on ${this.config.bindAddress}:${this.config.authPort} (auth) and ${this.config.acctPort} (acct)`);

    // Initialize managers
    await this.vlanManager.initialize();
    await this.accountingManager.initialize();

    // Import static VLAN mappings if provided
    if (this.config.vlanAssignment?.mappings) {
      await this.vlanManager.importMappings(this.config.vlanAssignment.mappings);
    }

    const cidrSecrets = this.buildClientSecretsMap();

    // Create the RADIUS server
    this.radiusServer = new plugins.smartradius.RadiusServer({
      authPort: this.config.authPort,
      acctPort: this.config.acctPort,
      bindAddress: this.config.bindAddress,
      cidrSecrets,
      authenticationHandler: this.handleAuthentication.bind(this),
      accountingHandler: this.handleAccounting.bind(this),
    });

    // Start the server
    await this.radiusServer.start();

    this.running = true;
    this.stats.startTime = Date.now();

    logger.log('info', `RADIUS server started with ${this.config.clients.length} configured clients`);
  }

  /**
   * Stop the RADIUS server
   */
  async stop(): Promise<void> {
    if (!this.running) {
      return;
    }

    logger.log('info', 'Stopping RADIUS server...');

    if (this.radiusServer) {
      await this.radiusServer.stop();
      this.radiusServer = undefined;
    }

    this.accountingManager.stop();

    this.running = false;
    logger.log('info', 'RADIUS server stopped');
  }

  /**
   * Handle authentication request
   */
  private async handleAuthentication(
    request: plugins.smartradius.IAuthenticationRequest,
  ): Promise<plugins.smartradius.IAuthenticationResponse> {
    this.stats.authRequests++;

    const authData: IAuthRequestData = {
      username: request.username || '',
      password: request.password,
      nasIpAddress: request.nasIpAddress || request.clientAddress || '',
      nasPort: request.nasPort,
      nasPortType: request.nasPortType !== undefined ? String(request.nasPortType) : undefined,
      nasIdentifier: request.nasIdentifier,
      calledStationId: request.calledStationId,
      callingStationId: request.callingStationId,
      serviceType: request.serviceType !== undefined ? String(request.serviceType) : undefined,
      framedMtu: request.framedMtu,
    };

    logger.log('debug', `RADIUS Auth Request: user=${authData.username}, NAS=${authData.nasIpAddress}`);

    // Perform MAC Authentication Bypass (MAB)
    // In MAB, the username is typically the MAC address
    const result = await this.performMabAuthentication(authData);

    if (result.success) {
      this.stats.authAccepts++;
      logger.log('info', `RADIUS Auth Accept: user=${authData.username}, VLAN=${result.vlanId}`);

      // Build response with VLAN attributes
      const response: plugins.smartradius.IAuthenticationResponse = {
        code: plugins.smartradius.ERadiusCode.AccessAccept,
        replyMessage: result.replyMessage,
      };

      // Add VLAN attributes if assigned
      if (result.vlanId !== undefined) {
        response.tunnelType = plugins.smartradius.ETunnelType.Vlan;
        response.tunnelMediumType = plugins.smartradius.ETunnelMediumType.Ieee802;
        response.tunnelPrivateGroupId = String(result.vlanId);
      }

      // Add session timeout if specified
      if (result.sessionTimeout) {
        response.sessionTimeout = result.sessionTimeout;
      }

      // Add idle timeout if specified
      if (result.idleTimeout) {
        response.idleTimeout = result.idleTimeout;
      }

      // Add framed IP if specified
      if (result.framedIpAddress) {
        response.framedIpAddress = result.framedIpAddress;
      }

      return response;
    } else {
      this.stats.authRejects++;
      logger.log('warn', `RADIUS Auth Reject: user=${authData.username}, reason=${result.rejectReason}`);

      return {
        code: plugins.smartradius.ERadiusCode.AccessReject,
        replyMessage: result.rejectReason || 'Access Denied',
      };
    }
  }

  /**
   * Handle accounting request
   */
  private async handleAccounting(
    request: plugins.smartradius.IAccountingRequest,
  ): Promise<plugins.smartradius.IAccountingResponse> {
    this.stats.accountingRequests++;

    if (!this.config.accounting?.enabled) {
      // Still respond even if not tracking
      return { success: true };
    }

    const statusType = request.statusType;
    const sessionId = request.sessionId || '';

    const accountingData = {
      sessionId,
      username: request.username || '',
      macAddress: request.callingStationId,
      nasIpAddress: request.nasIpAddress || request.clientAddress || '',
      nasPort: request.nasPort,
      nasPortType: request.nasPortType !== undefined ? String(request.nasPortType) : undefined,
      nasIdentifier: request.nasIdentifier,
      calledStationId: request.calledStationId,
      callingStationId: request.callingStationId,
      inputOctets: request.inputOctets,
      outputOctets: request.outputOctets,
      inputPackets: request.inputPackets,
      outputPackets: request.outputPackets,
      sessionTime: request.sessionTime,
      terminateCause: request.terminateCause !== undefined ? String(request.terminateCause) : undefined,
      framedIpAddress: request.framedIpAddress,
      serviceType: request.serviceType !== undefined ? String(request.serviceType) : undefined,
    };

    try {
      switch (statusType) {
        case plugins.smartradius.EAcctStatusType.Start:
          logger.log('debug', `RADIUS Acct Start: session=${sessionId}, user=${accountingData.username}`);
          await this.accountingManager.handleAccountingStart(accountingData);
          break;

        case plugins.smartradius.EAcctStatusType.Stop:
          logger.log('debug', `RADIUS Acct Stop: session=${sessionId}`);
          await this.accountingManager.handleAccountingStop(accountingData);
          break;

        case plugins.smartradius.EAcctStatusType.InterimUpdate:
          logger.log('debug', `RADIUS Acct Interim: session=${sessionId}`);
          await this.accountingManager.handleAccountingUpdate(accountingData);
          break;

        default:
          logger.log('debug', `RADIUS Acct Unknown status type: ${statusType}`);
      }
    } catch (error: unknown) {
      logger.log('error', `RADIUS accounting error: ${(error as Error).message}`);
    }

    return { success: true };
  }

  /**
   * Perform MAC Authentication Bypass
   */
  private async performMabAuthentication(data: IAuthRequestData): Promise<IRadiusAuthResult> {
    // Extract MAC address from username or CallingStationId
    const macAddress = this.extractMacAddress(data);

    if (!macAddress) {
      return {
        success: false,
        rejectReason: 'No MAC address found',
      };
    }

    // Look up VLAN assignment
    const vlanResult = this.vlanManager.assignVlan(macAddress);

    if (!vlanResult.assigned) {
      return {
        success: false,
        rejectReason: 'Unknown MAC address',
      };
    }

    // Build successful result
    const result: IRadiusAuthResult = {
      success: true,
      vlanId: vlanResult.vlan,
      replyMessage: vlanResult.isDefault
        ? `Assigned to default VLAN ${vlanResult.vlan}`
        : `Assigned to VLAN ${vlanResult.vlan}`,
    };

    // Apply any additional settings from the matched rule
    if (vlanResult.matchedRule) {
      // Future: Add session timeout, idle timeout, etc. from rule
    }

    return result;
  }

  /**
   * Extract MAC address from authentication data
   */
  private extractMacAddress(data: IAuthRequestData): string | null {
    // Try CallingStationId first (most common for MAB)
    if (data.callingStationId) {
      return this.normalizeMac(data.callingStationId);
    }

    // Try username (often MAC address in MAB)
    if (data.username && this.looksLikeMac(data.username)) {
      return this.normalizeMac(data.username);
    }

    return null;
  }

  /**
   * Check if a string looks like a MAC address
   */
  private looksLikeMac(value: string): boolean {
    // Remove common separators and check length
    const cleaned = value.replace(/[-:. ]/g, '');
    return /^[0-9a-fA-F]{12}$/.test(cleaned);
  }

  /**
   * Normalize MAC address format
   */
  private normalizeMac(mac: string): string {
    return this.vlanManager.normalizeMac(mac);
  }

  /**
   * Build client secrets map from configuration
   */
  private buildClientSecretsMap(): Record<string, string> {
    const cidrSecrets: Record<string, string> = {};

    for (const client of this.config.clients) {
      if (!client.enabled) {
        continue;
      }

      cidrSecrets[client.ipRange] = client.secret;
    }

    return cidrSecrets;
  }

  /**
   * Add a RADIUS client
   */
  async addClient(client: IRadiusClient): Promise<void> {
    // Check if client already exists
    const existingIndex = this.config.clients.findIndex(c => c.name === client.name);
    const previousClient = existingIndex >= 0 ? this.config.clients[existingIndex] : undefined;
    if (existingIndex >= 0) {
      this.config.clients[existingIndex] = client;
    } else {
      this.config.clients.push(client);
    }

    if (this.running && this.radiusServer) {
      if (previousClient) {
        this.radiusServer.removeNetworkSecret(previousClient.ipRange);
      }
      if (client.enabled) {
        this.radiusServer.setNetworkSecret(client.ipRange, client.secret);
      }
    }

    logger.log('info', `RADIUS client ${client.enabled ? 'added' : 'disabled'}: ${client.name} (${client.ipRange})`);
  }

  /**
   * Remove a RADIUS client
   */
  removeClient(name: string): boolean {
    const index = this.config.clients.findIndex(c => c.name === name);
    if (index >= 0) {
      const client = this.config.clients[index];
      this.config.clients.splice(index, 1);

      if (this.radiusServer) {
        this.radiusServer.removeNetworkSecret(client.ipRange);
      }

      logger.log('info', `RADIUS client removed: ${name}`);
      return true;
    }
    return false;
  }

  /**
   * Get configured clients
   */
  getClients(): IRadiusClient[] {
    return [...this.config.clients];
  }

  /**
   * Get VLAN manager for direct access to VLAN operations
   */
  getVlanManager(): VlanManager {
    return this.vlanManager;
  }

  /**
   * Get accounting manager for direct access to accounting operations
   */
  getAccountingManager(): AccountingManager {
    return this.accountingManager;
  }

  /**
   * Get server statistics
   */
  getStats(): {
    running: boolean;
    uptime: number;
    authRequests: number;
    authAccepts: number;
    authRejects: number;
    accountingRequests: number;
    activeSessions: number;
    vlanMappings: number;
    clients: number;
  } {
    return {
      running: this.running,
      uptime: this.running ? Date.now() - this.stats.startTime : 0,
      authRequests: this.stats.authRequests,
      authAccepts: this.stats.authAccepts,
      authRejects: this.stats.authRejects,
      accountingRequests: this.stats.accountingRequests,
      activeSessions: this.accountingManager.getStats().activeSessions,
      vlanMappings: this.vlanManager.getStats().totalMappings,
      clients: this.config.clients.filter(c => c.enabled).length,
    };
  }

  /**
   * Check if server is running
   */
  isRunning(): boolean {
    return this.running;
  }
}
