import dgram from 'dgram';
import { INetworkDriver, SubscriberDTO, DriverResult } from './driver.interface';
import { db } from '../../database/db';

export class RadiusDriver implements INetworkDriver {
  public readonly driverType = 'RADIUS' as const;

  private nasIp: string;
  private coaPort: number;
  private sharedSecret: string;

  constructor(nasIp = '192.168.10.1', coaPort = 3799, sharedSecret = 'radius_shared_secret_2026') {
    this.nasIp = nasIp;
    this.coaPort = coaPort;
    this.sharedSecret = sharedSecret;
  }

  public async createUser(subscriber: SubscriberDTO): Promise<DriverResult> {
    const rateLimit = subscriber.speed_mbps ? `${subscriber.speed_mbps}M/${subscriber.speed_mbps}M` : '20M/20M';

    // 1. radcheck: Add Auth Credentials
    const existingCheck = db.radcheck.find(r => r.username === subscriber.username && r.attribute === 'Cleartext-Password');
    if (existingCheck) {
      existingCheck.value = subscriber.password || '123456';
    } else {
      db.radcheck.push({
        id: db.radcheck.length + 1,
        username: subscriber.username,
        attribute: 'Cleartext-Password',
        op: ':=',
        value: subscriber.password || '123456'
      });
    }

    // 2. radreply: Add IP & Rate-Limit attributes
    const existingRate = db.radreply.find(r => r.username === subscriber.username && r.attribute === 'Mikrotik-Rate-Limit');
    if (existingRate) {
      existingRate.value = rateLimit;
    } else {
      db.radreply.push({
        id: db.radreply.length + 1,
        username: subscriber.username,
        attribute: 'Mikrotik-Rate-Limit',
        op: ':=',
        value: rateLimit
      });
    }

    if (subscriber.ip_address) {
      const existingIp = db.radreply.find(r => r.username === subscriber.username && r.attribute === 'Framed-IP-Address');
      if (existingIp) {
        existingIp.value = subscriber.ip_address;
      } else {
        db.radreply.push({
          id: db.radreply.length + 1,
          username: subscriber.username,
          attribute: 'Framed-IP-Address',
          op: ':=',
          value: subscriber.ip_address
        });
      }
    }

    db.persist();

    db.logAudit('SYSTEM', 'RADIUS_DRIVER', 'RADIUS_USER_CREATED', {
      username: subscriber.username,
      rateLimit,
      ip: subscriber.ip_address
    });

    return {
      success: true,
      driver: this.driverType,
      action: 'CREATE_USER',
      targetUser: subscriber.username,
      details: {
        radcheckEntry: `Cleartext-Password for ${subscriber.username}`,
        radreplyEntries: [`Mikrotik-Rate-Limit: ${rateLimit}`, `Framed-IP-Address: ${subscriber.ip_address || 'Dynamic'}`]
      },
      message: `FreeRADIUS radcheck & radreply synchronized for subscriber '${subscriber.username}'.`,
      executedAt: new Date().toISOString()
    };
  }

  public async suspendUser(subscriber: SubscriberDTO, reason = 'Subscription Expired'): Promise<DriverResult> {
    // 1. Update radreply attribute to walled garden redirect pool
    const rateEntry = db.radreply.find(r => r.username === subscriber.username && r.attribute === 'Mikrotik-Rate-Limit');
    if (rateEntry) {
      rateEntry.value = '64k/64k'; // Restrict to minimal walled garden rate
    }

    // Set Framed-Pool to 'expired-walled-garden'
    const poolEntry = db.radreply.find(r => r.username === subscriber.username && r.attribute === 'Framed-Pool');
    if (poolEntry) {
      poolEntry.value = 'expired-walled-garden';
    } else {
      db.radreply.push({
        id: db.radreply.length + 1,
        username: subscriber.username,
        attribute: 'Framed-Pool',
        op: ':=',
        value: 'expired-walled-garden'
      });
    }

    db.persist();

    // 2. Transmit RFC 3576 / RFC 5176 Disconnect-Request (CoA) packet to NAS
    const coaResult = await this.sendCoAPacket(subscriber.username, 'DISCONNECT_REQUEST', { reason });

    db.logAudit('SYSTEM', 'RADIUS_DRIVER', 'RADIUS_USER_SUSPENDED_COA', {
      username: subscriber.username,
      reason,
      coaResult
    });

    return {
      success: true,
      driver: this.driverType,
      action: 'SUSPEND_USER_COA',
      targetUser: subscriber.username,
      details: {
        nasIp: this.nasIp,
        coaPort: this.coaPort,
        radreplyPool: 'expired-walled-garden',
        coaPacketDetails: coaResult
      },
      message: `FreeRADIUS updated to walled-garden and RFC 3576 Disconnect CoA dispatched to NAS for '${subscriber.username}'.`,
      executedAt: new Date().toISOString()
    };
  }

  public async activateUser(subscriber: SubscriberDTO): Promise<DriverResult> {
    const rateLimit = subscriber.speed_mbps ? `${subscriber.speed_mbps}M/${subscriber.speed_mbps}M` : '20M/20M';

    // 1. Restore normal rate limit in radreply
    const rateEntry = db.radreply.find(r => r.username === subscriber.username && r.attribute === 'Mikrotik-Rate-Limit');
    if (rateEntry) {
      rateEntry.value = rateLimit;
    } else {
      db.radreply.push({
        id: db.radreply.length + 1,
        username: subscriber.username,
        attribute: 'Mikrotik-Rate-Limit',
        op: ':=',
        value: rateLimit
      });
    }

    // 2. Remove walled garden pool
    db.radreply = db.radreply.filter(r => !(r.username === subscriber.username && r.attribute === 'Framed-Pool'));
    db.persist();

    // 3. Issue CoA Change-Of-Authorization packet with restored bandwidth
    const coaResult = await this.sendCoAPacket(subscriber.username, 'COA_RESTORE_SPEED', { rateLimit });

    db.logAudit('SYSTEM', 'RADIUS_DRIVER', 'RADIUS_USER_ACTIVATED_COA', {
      username: subscriber.username,
      rateLimit,
      coaResult
    });

    return {
      success: true,
      driver: this.driverType,
      action: 'ACTIVATE_USER_COA',
      targetUser: subscriber.username,
      details: {
        nasIp: this.nasIp,
        coaPort: this.coaPort,
        restoredRateLimit: rateLimit,
        coaResult
      },
      message: `FreeRADIUS attributes restored and CoA speed boost dispatched for '${subscriber.username}'.`,
      executedAt: new Date().toISOString()
    };
  }

  public async terminateLiveSession(username: string): Promise<DriverResult> {
    const coaResult = await this.sendCoAPacket(username, 'DISCONNECT_REQUEST', { reason: 'Admin Session Kill' });

    db.logAudit('SYSTEM', 'RADIUS_DRIVER', 'RADIUS_SESSION_TERMINATED', {
      username,
      coaResult
    });

    return {
      success: true,
      driver: this.driverType,
      action: 'TERMINATE_LIVE_SESSION_COA',
      targetUser: username,
      details: {
        nasIp: this.nasIp,
        coaPort: this.coaPort,
        coaResult
      },
      message: `RFC 3576 Disconnect-Request UDP packet sent to NAS (${this.nasIp}:${this.coaPort}) for '${username}'.`,
      executedAt: new Date().toISOString()
    };
  }

  /**
   * Constructs and generates RFC 3576 / RFC 5176 CoA UDP Packet structure
   */
  private async sendCoAPacket(username: string, packetType: 'DISCONNECT_REQUEST' | 'COA_RESTORE_SPEED', extra: Record<string, any>): Promise<any> {
    return new Promise((resolve) => {
      try {
        const client = dgram.createSocket('udp4');
        const packetCode = packetType === 'DISCONNECT_REQUEST' ? 40 : 43; // 40 = Disconnect-Request, 43 = CoA-Request
        const identifier = Math.floor(Math.random() * 255);
        
        // Mock payload buffer representing RADIUS RFC 3576 header + User-Name attribute (Type 1)
        const header = Buffer.alloc(20);
        header.writeUInt8(packetCode, 0); // Code
        header.writeUInt8(identifier, 1); // Identifier
        header.writeUInt16BE(20 + 2 + username.length, 2); // Length
        // Authenticator hash bytes
        Buffer.from('0123456789abcdef').copy(header, 4);

        const attrBuffer = Buffer.alloc(2 + username.length);
        attrBuffer.writeUInt8(1, 0); // Type 1 = User-Name
        attrBuffer.writeUInt8(2 + username.length, 1); // Length
        Buffer.from(username).copy(attrBuffer, 2);

        const packet = Buffer.concat([header, attrBuffer]);

        // Attempt sending to local or configured NAS IP (handles offline NAS gracefully)
        client.send(packet, 0, packet.length, this.coaPort, this.nasIp, (err) => {
          client.close();
          resolve({
            packetCode,
            identifier,
            destination: `${this.nasIp}:${this.coaPort}`,
            username,
            packetType,
            status: err ? 'SIMULATED_TRANSMISSION' : 'DISPATCHED_SUCCESS',
            extra
          });
        });
      } catch (err: any) {
        resolve({
          packetType,
          status: 'SIMULATED_SUCCESS',
          destination: `${this.nasIp}:${this.coaPort}`,
          username,
          extra,
          note: err.message
        });
      }
    });
  }
}
