import axios, { AxiosInstance } from "axios";
import https from "https";
import { events } from "../globalEvents";
import {
  ApGroup,
  ApRadioWifiUsage,
  ApWifiLimits,
  CreateSiteOptions,
  Device,
  DeviceRadio,
  DeviceState,
  DeviceUplink,
  Network,
  PrivatePSK,
  PrivatePSKCreateInput,
  SiteListItem,
  SiteOverview,
  SubsystemHealth,
  SysInfo,
  UserGroup,
  UserGroupCreateInput,
  WlanConf,
  WlanConfRaw,
  WlanConfUpdate,
  WlanGroup,
  WlanGroupCreateInput,
} from "./unifiTypes";

export class UnifiClient {
  private client: AxiosInstance;

  constructor(baseURL: string) {
    this.client = axios.create({
      baseURL,
      validateStatus: (s) => s >= 200 && s < 400,
      httpsAgent: new https.Agent({ rejectUnauthorized: false }),
    });
  }

  private persistSession(res: { headers: Record<string, unknown> }): void {
    // Cookies
    const raw = res.headers["set-cookie"];
    if (raw) {
      const cookies = (Array.isArray(raw) ? raw : [raw]) as string[];
      const cookieString = cookies.map((c) => c.split(";")[0]).join("; ");
      this.client.defaults.headers.common["Cookie"] = cookieString;
    }
    // CSRF token (UniFi OS)
    const csrf = res.headers["x-csrf-token"];
    if (typeof csrf === "string") {
      this.client.defaults.headers.common["X-CSRF-Token"] = csrf;
    }
  }

  async login(username: string, password: string): Promise<void> {
    const body = { username, password };

    try {
      // UniFi OS
      const res = await this.client.post("/api/auth/login", body);
      events.emit("unifi:login:ok", { type: "unifi-os", status: res.status });
      this.persistSession(res);
    } catch (e) {
      // Legacy controller
      events.emit("unifi:login:fallback");
      const res = await this.client.post("/api/login", body);
      events.emit("unifi:login:ok", { type: "legacy", status: res.status });
      this.persistSession(res);
    }
  }

  private async fetchWlanConfRaw(site: string): Promise<WlanConfRaw[]> {
    const paths = [
      `/proxy/network/api/s/${site}/rest/wlanconf`,
      `/api/s/${site}/rest/wlanconf`,
    ];

    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const data = (res.data?.data ?? res.data) as WlanConfRaw[];
        events.emit("unifi:wlan:fetched", { path });
        return data;
      } catch (e) {
        events.emit("unifi:wlan:fetch_failed", {
          path,
          status: axios.isAxiosError(e) ? e.response?.status : e,
        });
      }
    }

    throw new Error("Could not fetch WLAN config from any known path");
  }

  private static parseWlanConf(w: any): WlanConf {
    return {
      id: w._id,
      name: (w.name || w.ssid || "").toString(),
      siteId: w.site_id ?? "",
      enabled: w.enabled ?? true,
      security: w.security ?? "open",
      wpaMode: w.wpa_mode ?? "",
      wpaEnc: w.wpa_enc ?? "",
      wpa3Support: w.wpa3_support ?? false,
      wpa3Transition: w.wpa3_transition ?? false,
      wpa3FastRoaming: w.wpa3_fast_roaming ?? false,
      wpa3Enhanced192: w.wpa3_enhanced_192 ?? false,
      passphrase: typeof w.x_passphrase === "string" ? w.x_passphrase : null,
      passphraseAutogenerated: w.passphrase_autogenerated ?? false,
      hideSSID: w.hide_ssid ?? false,
      isGuest: w.is_guest ?? false,
      band: w.wlan_band ?? "both",
      bands: w.wlan_bands ?? [],
      networkconfId: w.networkconf_id ?? "",
      usergroupId: w.usergroup_id ?? "",
      apGroupIds: w.ap_group_ids ?? [],
      apGroupMode: w.ap_group_mode ?? "devices",
      pmfMode: w.pmf_mode ?? "disabled",
      groupRekey: w.group_rekey ?? 0,
      dtimMode: w.dtim_mode ?? "default",
      dtimNg: w.dtim_ng ?? 1,
      dtimNa: w.dtim_na ?? 3,
      dtim6e: w.dtim_6e ?? 3,
      l2Isolation: w.l2_isolation ?? false,
      fastRoamingEnabled: w.fast_roaming_enabled ?? false,
      bssTransition: w.bss_transition ?? false,
      uapsdEnabled: w.uapsd_enabled ?? false,
      iappEnabled: w.iapp_enabled ?? false,
      proxyArp: w.proxy_arp ?? false,
      mcastenhanceEnabled: w.mcastenhance_enabled ?? false,
      macFilterEnabled: w.mac_filter_enabled ?? false,
      macFilterPolicy: w.mac_filter_policy ?? "allow",
      macFilterList: w.mac_filter_list ?? [],
      radiusDasEnabled: w.radius_das_enabled ?? false,
      radiusMacAuthEnabled: w.radius_mac_auth_enabled ?? false,
      radiusMacaclFormat: w.radius_macacl_format ?? "none_lower",
      minrateSettingPreference: w.minrate_setting_preference ?? "auto",
      minrateNgEnabled: w.minrate_ng_enabled ?? false,
      minrateNgDataRateKbps: w.minrate_ng_data_rate_kbps ?? 1000,
      minrateNgAdvertisingRates: w.minrate_ng_advertising_rates ?? false,
      minrateNaEnabled: w.minrate_na_enabled ?? false,
      minrateNaDataRateKbps: w.minrate_na_data_rate_kbps ?? 6000,
      minrateNaAdvertisingRates: w.minrate_na_advertising_rates ?? false,
      settingPreference: w.setting_preference ?? "auto",
      no2ghzOui: w.no2ghz_oui ?? false,
      privatePreSharedKeysEnabled: w.private_preshared_keys_enabled ?? false,
      privatePreSharedKeys: w.private_preshared_keys ?? [],
      saeGroups: w.sae_groups ?? [],
      saePsk: w.sae_psk ?? [],
      schedule: w.schedule ?? [],
      scheduleWithDuration: w.schedule_with_duration ?? [],
      bcFilterList: w.bc_filter_list ?? [],
      externalId: w.external_id ?? null,
    };
  }

  async getWlanConf(site: string): Promise<WlanConf[]> {
    const raw = await this.fetchWlanConfRaw(site);
    return raw.map(UnifiClient.parseWlanConf);
  }

  async updateWlanConf(
    site: string,
    wlanId: string,
    updates: WlanConfUpdate,
  ): Promise<WlanConf> {
    const paths = [
      `/proxy/network/api/s/${site}/rest/wlanconf/${wlanId}`,
      `/api/s/${site}/rest/wlanconf/${wlanId}`,
    ];

    // Fetch current WLAN to check if a RADIUS profile is configured.
    // The controller rejects RADIUS fields when no profile is set.
    const currentWlans = await this.getWlanConf(site);
    const currentWlan = currentWlans.find((w) => w.id === wlanId);
    const hasRadius =
      currentWlan?.security === "wpaeap" || updates.security === "wpaeap";

    if (!hasRadius) {
      delete updates.radius_das_enabled;
      delete updates.radius_mac_auth_enabled;
    }

    for (const path of paths) {
      try {
        const res = await this.client.put(path, updates);
        const raw = (res.data?.data?.[0] ?? res.data) as any;
        return UnifiClient.parseWlanConf(raw);
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        // Try next path on 404/401, throw on other errors
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw new Error(
            `Failed to update WLAN ${wlanId}: ${e.response.status} ${JSON.stringify(e.response.data)}`,
          );
        }
      }
    }

    throw new Error("Could not update WLAN config from any known path");
  }

  async getAllSites(): Promise<SiteListItem[]> {
    const paths = ["/proxy/network/api/self/sites", "/api/self/sites"];

    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const raw = (res.data?.data ?? res.data) as any[];
        return raw.map(
          (s: any): SiteListItem => ({
            id: s._id,
            name: s.name,
            description: s.desc ?? "",
            deviceCount: s.device_count ?? 0,
            role: s.role ?? "",
          }),
        );
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw e;
        }
      }
    }

    throw new Error("Could not fetch sites from any known path");
  }

  async getSiteOverview(site: string): Promise<SiteOverview> {
    const prefixes = ["/proxy/network", ""];

    for (const prefix of prefixes) {
      try {
        const [healthRes, sysInfoRes, sitesRes] = await Promise.all([
          this.client.get(`${prefix}/api/s/${site}/stat/health`),
          this.client.get(`${prefix}/api/s/${site}/stat/sysinfo`),
          this.client.get(`${prefix}/api/self/sites`),
        ]);

        const healthRaw = (healthRes.data?.data ?? healthRes.data) as any[];
        const sysInfoRaw = (sysInfoRes.data?.data?.[0] ??
          sysInfoRes.data) as any;
        const sitesRaw = (sitesRes.data?.data ?? sitesRes.data) as any[];

        const siteRaw = sitesRaw.find((s: any) => s.name === site);
        if (!siteRaw) throw new Error(`Site "${site}" not found in sites list`);

        const health: SubsystemHealth[] = healthRaw.map((h: any) => ({
          subsystem: h.subsystem,
          status: h.status,
          numUser: h.num_user,
          numGuest: h.num_guest,
          numIot: h.num_iot,
          txBytesR: h["tx_bytes-r"],
          rxBytesR: h["rx_bytes-r"],
          numAp: h.num_ap,
          numSw: h.num_sw,
          numGw: h.num_gw,
          numAdopted: h.num_adopted,
          numDisconnected: h.num_disconnected,
          numPending: h.num_pending,
          numDisabled: h.num_disabled,
        }));

        const sysInfo: SysInfo = {
          name: sysInfoRaw.name,
          hostname: sysInfoRaw.hostname,
          version: sysInfoRaw.version,
          build: sysInfoRaw.build,
          timezone: sysInfoRaw.timezone,
          uptime: sysInfoRaw.uptime,
          ipAddresses: sysInfoRaw.ip_addrs ?? [],
          updateAvailable: sysInfoRaw.update_available ?? false,
          isCloudConsole: sysInfoRaw.is_cloud_console ?? false,
          dataRetentionDays: sysInfoRaw.data_retention_days ?? 0,
          informPort: sysInfoRaw.inform_port,
          httpsPort: sysInfoRaw.https_port,
          unsupportedDeviceCount: sysInfoRaw.unsupported_device_count ?? 0,
        };

        return {
          site: {
            id: siteRaw._id,
            name: siteRaw.name,
            description: siteRaw.desc ?? "",
            deviceCount: siteRaw.device_count ?? 0,
            role: siteRaw.role ?? "",
          },
          health,
          sysInfo,
        };
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw e;
        }
      }
    }

    throw new Error("Could not fetch site overview from any known path");
  }

  private static parseDeviceState(state: number): DeviceState {
    const map: Record<number, DeviceState> = {
      0: "disconnected",
      1: "connected",
      2: "pending",
      4: "adopting",
      5: "adopting",
    };
    return map[state] ?? "unknown";
  }

  async getDevices(site: string): Promise<Device[]> {
    const paths = [
      `/proxy/network/api/s/${site}/stat/device`,
      `/api/s/${site}/stat/device`,
    ];

    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const raw = (res.data?.data ?? res.data) as any[];

        return raw.map((d: any): Device => {
          const uplink: DeviceUplink | null = d.uplink
            ? {
                type: d.uplink.type,
                mac: d.uplink.uplink_mac,
                ip: d.uplink.uplink_remote_ip,
                uplinkRemotePort: d.uplink.uplink_remote_port,
                speed: d.uplink.speed,
                fullDuplex: d.uplink.full_duplex,
              }
            : null;

          const radios: DeviceRadio[] = (d.radio_table ?? []).map(
            (r: any, i: number) => {
              const stats = d.radio_table_stats?.[i] ?? {};
              return {
                name: r.name ?? r.radio,
                radio: r.radio,
                channel: r.channel,
                txPower: r.tx_power,
                txPowerMode: r.tx_power_mode,
                minRssiEnabled: r.min_rssi_enabled ?? false,
                numSta: stats.num_sta ?? 0,
                satisfaction: stats.satisfaction ?? null,
              };
            },
          );

          return {
            id: d._id,
            mac: d.mac,
            ip: d.ip ?? "",
            name: d.name ?? d.mac,
            model: d.model,
            shortname: d.shortname ?? d.model,
            type: d.type,
            version: d.version ?? "",
            serial: d.serial ?? "",
            state: UnifiClient.parseDeviceState(d.state),
            adopted: d.adopted ?? false,
            uptime: d.uptime ?? 0,
            lastSeen: d.last_seen ?? 0,
            upgradable: d.upgradable ?? false,
            satisfaction: d.satisfaction ?? null,
            numClients: d.num_sta ?? 0,
            numUserClients: d["user-num_sta"] ?? 0,
            numGuestClients: d["guest-num_sta"] ?? 0,
            txBytes: d.tx_bytes ?? 0,
            rxBytes: d.rx_bytes ?? 0,
            uplink,
            radios,
            modelInLts: d.model_in_lts ?? false,
            modelInEol: d.model_in_eol ?? false,
          };
        });
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw e;
        }
      }
    }

    throw new Error("Could not fetch devices from any known path");
  }

  async getNetworks(site: string): Promise<Network[]> {
    const paths = [
      `/proxy/network/api/s/${site}/rest/networkconf`,
      `/api/s/${site}/rest/networkconf`,
    ];

    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const raw = (res.data?.data ?? res.data) as any[];

        return raw.map(
          (n: any): Network => ({
            id: n._id,
            name: n.name ?? "",
            purpose: n.purpose ?? "corporate",
            enabled: n.enabled ?? true,
            ipSubnet: n.ip_subnet ?? null,
            vlan: n.vlan != null ? Number(n.vlan) : null,
            vlanEnabled: n.vlan_enabled ?? false,
            isNat: n.is_nat ?? false,
            domainName: n.domain_name ?? null,
            networkGroup: n.networkgroup ?? null,
            dhcpdEnabled: n.dhcpd_enabled ?? false,
            dhcpdStart: n.dhcpd_start ?? null,
            dhcpdStop: n.dhcpd_stop ?? null,
            dhcpdLeasetime: n.dhcpd_leasetime ?? null,
            dhcpRelayEnabled: n.dhcp_relay_enabled ?? false,
            dhcpGuardEnabled: n.dhcpguard_enabled ?? false,
            igmpSnooping: n.igmp_snooping ?? false,
            ipv6Enabled: n.ipv6_enabled ?? false,
            ipv6InterfaceType: n.ipv6_interface_type ?? null,
            internetAccessEnabled: n.internet_access_enabled ?? null,
          }),
        );
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw e;
        }
      }
    }

    throw new Error("Could not fetch networks from any known path");
  }

  async createSite(description: string): Promise<SiteListItem> {
    const paths = [
      "/proxy/network/api/s/default/cmd/sitemgr",
      "/api/s/default/cmd/sitemgr",
    ];

    const body = { cmd: "add-site", desc: description };

    for (const path of paths) {
      try {
        const res = await this.client.post(path, body);
        const raw = (res.data?.data?.[0] ?? res.data) as any;
        return {
          id: raw._id,
          name: raw.name,
          description: raw.desc ?? description,
          deviceCount: raw.device_count ?? 0,
          role: raw.role ?? "",
        };
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw new Error(
            `Failed to create site: ${e.response.status} ${JSON.stringify(e.response.data)}`,
          );
        }
      }
    }

    throw new Error("Could not create site from any known path");
  }

  // --- WLAN Groups ---

  async getWlanGroups(site: string): Promise<WlanGroup[]> {
    const paths = [
      `/proxy/network/api/s/${site}/rest/wlangroup`,
      `/api/s/${site}/rest/wlangroup`,
    ];

    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const raw = (res.data?.data ?? res.data) as any[];
        return raw.map(
          (g: any): WlanGroup => ({
            id: g._id,
            name: g.name ?? "",
            siteId: g.site_id ?? "",
            noDelete: g.attr_no_delete ?? false,
            noEdit: g.attr_no_edit ?? false,
            hidden: g.attr_hidden ?? false,
          }),
        );
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        )
          throw e;
      }
    }

    throw new Error("Could not fetch WLAN groups from any known path");
  }

  async createWlanGroup(
    site: string,
    input: WlanGroupCreateInput,
  ): Promise<WlanGroup> {
    const paths = [
      `/proxy/network/api/s/${site}/rest/wlangroup`,
      `/api/s/${site}/rest/wlangroup`,
    ];

    const body: Record<string, unknown> = { name: input.name };

    for (const path of paths) {
      try {
        const res = await this.client.post(path, body);
        const raw = (res.data?.data?.[0] ?? res.data) as any;
        return {
          id: raw._id,
          name: raw.name ?? input.name,
          siteId: raw.site_id ?? "",
          noDelete: raw.attr_no_delete ?? false,
          noEdit: raw.attr_no_edit ?? false,
          hidden: raw.attr_hidden ?? false,
        };
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw new Error(
            `Failed to create WLAN group: ${e.response.status} ${JSON.stringify(e.response.data)}`,
          );
        }
      }
    }

    throw new Error("Could not create WLAN group from any known path");
  }

  // --- User Groups (Speed Profiles) ---

  async getUserGroups(site: string): Promise<UserGroup[]> {
    const paths = [
      `/proxy/network/api/s/${site}/rest/usergroup`,
      `/api/s/${site}/rest/usergroup`,
    ];

    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const raw = (res.data?.data ?? res.data) as any[];
        return raw.map(
          (g: any): UserGroup => ({
            id: g._id,
            name: g.name ?? "",
            siteId: g.site_id ?? "",
            noDelete: g.attr_no_delete ?? false,
            downloadLimitKbps: g.qos_rate_max_down ?? -1,
            uploadLimitKbps: g.qos_rate_max_up ?? -1,
          }),
        );
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        )
          throw e;
      }
    }

    throw new Error("Could not fetch user groups from any known path");
  }

  async createUserGroup(
    site: string,
    input: UserGroupCreateInput,
  ): Promise<UserGroup> {
    const paths = [
      `/proxy/network/api/s/${site}/rest/usergroup`,
      `/api/s/${site}/rest/usergroup`,
    ];

    const body: Record<string, unknown> = { name: input.name };
    if (input.downloadLimitKbps !== undefined)
      body.qos_rate_max_down = input.downloadLimitKbps;
    if (input.uploadLimitKbps !== undefined)
      body.qos_rate_max_up = input.uploadLimitKbps;

    for (const path of paths) {
      try {
        const res = await this.client.post(path, body);
        const raw = (res.data?.data?.[0] ?? res.data) as any;
        return {
          id: raw._id,
          name: raw.name ?? input.name,
          siteId: raw.site_id ?? "",
          noDelete: raw.attr_no_delete ?? false,
          downloadLimitKbps: raw.qos_rate_max_down ?? -1,
          uploadLimitKbps: raw.qos_rate_max_up ?? -1,
        };
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw new Error(
            `Failed to create user group: ${e.response.status} ${JSON.stringify(e.response.data)}`,
          );
        }
      }
    }

    throw new Error("Could not create user group from any known path");
  }

  // --- AP Groups ---

  async getApGroups(site: string): Promise<ApGroup[]> {
    const paths = [
      `/proxy/network/v2/api/site/${site}/apgroups`,
      `/v2/api/site/${site}/apgroups`,
    ];

    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const raw = (res.data?.data ?? res.data) as any[];
        return raw.map(
          (g: any): ApGroup => ({
            id: g._id,
            name: g.name ?? "",
            deviceMacs: g.device_macs ?? [],
            noDelete: g.attr_no_delete ?? false,
            forWlanconf: g.for_wlanconf ?? false,
          }),
        );
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        )
          throw e;
      }
    }

    throw new Error("Could not fetch AP groups from any known path");
  }

  async createApGroup(
    site: string,
    name: string,
    deviceMacs: string[],
    forWlanconf: boolean = false,
  ): Promise<ApGroup> {
    const paths = [
      `/proxy/network/v2/api/site/${site}/apgroups`,
      `/v2/api/site/${site}/apgroups`,
    ];

    const body = {
      name,
      device_macs: deviceMacs,
      for_wlanconf: forWlanconf,
    };

    for (const path of paths) {
      try {
        const res = await this.client.post(path, body);
        const raw = (res.data?.data?.[0] ?? res.data) as any;
        return {
          id: raw._id,
          name: raw.name ?? name,
          deviceMacs: raw.device_macs ?? deviceMacs,
          noDelete: raw.attr_no_delete ?? false,
          forWlanconf: raw.for_wlanconf ?? forWlanconf,
        };
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw new Error(
            `Failed to create AP group: ${e.response.status} ${JSON.stringify(e.response.data)}`,
          );
        }
      }
    }

    throw new Error("Could not create AP group from any known path");
  }

  async updateApGroup(
    site: string,
    groupId: string,
    deviceMacs: string[],
  ): Promise<ApGroup> {
    const paths = [
      `/proxy/network/v2/api/site/${site}/apgroups/${groupId}`,
      `/v2/api/site/${site}/apgroups/${groupId}`,
    ];

    const body = {
      name: "devices_ap_group",
      device_macs: deviceMacs,
      for_wlanconf: true,
    };

    for (const path of paths) {
      try {
        const res = await this.client.put(path, body);
        const raw = (res.data?.data?.[0] ?? res.data) as any;
        return {
          id: raw._id ?? groupId,
          name: raw.name ?? "devices_ap_group",
          deviceMacs: raw.device_macs ?? deviceMacs,
          noDelete: raw.attr_no_delete ?? false,
          forWlanconf: raw.for_wlanconf ?? true,
        };
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw new Error(
            `Failed to update AP group: ${e.response.status} ${JSON.stringify(e.response.data)}`,
          );
        }
      }
    }

    throw new Error("Could not update AP group from any known path");
  }

  // --- Access Points ---

  async getAccessPoints(site: string): Promise<Device[]> {
    const devices = await this.getDevices(site);
    return devices.filter((d) => d.type === "uap");
  }

  // --- WiFi Limits ---

  async getWifiLimits(site: string): Promise<ApWifiLimits[]> {
    const SSID_LIMIT_PER_RADIO = 8;

    const paths = [
      `/proxy/network/api/s/${site}/stat/device`,
      `/api/s/${site}/stat/device`,
    ];

    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const raw = (res.data?.data ?? res.data) as any[];
        const aps = raw.filter((d: any) => d.type === "uap");

        return aps.map((ap: any): ApWifiLimits => {
          const vapTable: any[] = ap.vap_table ?? [];
          const radioMap = new Map<string, { wlanNames: Set<string> }>();

          for (const vap of vapTable) {
            if (!vap.up || !vap.radio) continue;
            if (!radioMap.has(vap.radio)) {
              radioMap.set(vap.radio, { wlanNames: new Set() });
            }
            if (vap.essid) {
              radioMap.get(vap.radio)!.wlanNames.add(vap.essid);
            }
          }

          const radioBandMap: Record<string, string> = {
            ng: "2g",
            na: "5g",
            "6e": "6e",
          };

          const radios: ApRadioWifiUsage[] = Array.from(radioMap.entries()).map(
            ([radio, data]): ApRadioWifiUsage => ({
              radio,
              band: radioBandMap[radio] ?? radio,
              activeWlans: data.wlanNames.size,
              limit: SSID_LIMIT_PER_RADIO,
              remaining: Math.max(
                0,
                SSID_LIMIT_PER_RADIO - data.wlanNames.size,
              ),
              wlanNames: Array.from(data.wlanNames),
            }),
          );

          return {
            apId: ap._id,
            apName: ap.name ?? ap.mac,
            mac: ap.mac,
            model: ap.model ?? "",
            radios,
          };
        });
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        )
          throw e;
      }
    }

    throw new Error("Could not fetch WiFi limits from any known path");
  }

  // --- Private Pre-Shared Keys ---

  private static parsePPSKs(raw: any[]): PrivatePSK[] {
    return raw.map(
      (p: any): PrivatePSK => ({
        key: p.key ?? "",
        name: p.name ?? "",
        mac: p.mac ?? null,
        vlanId: p.vlan_id ?? null,
      }),
    );
  }

  async getPrivatePSKs(site: string, wlanId: string): Promise<PrivatePSK[]> {
    const paths = [
      `/proxy/network/api/s/${site}/rest/wlanconf/${wlanId}`,
      `/api/s/${site}/rest/wlanconf/${wlanId}`,
    ];

    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const raw = (res.data?.data?.[0] ?? res.data) as any;
        return UnifiClient.parsePPSKs(raw.private_preshared_keys ?? []);
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        )
          throw e;
      }
    }

    throw new Error("Could not fetch PPSKs from any known path");
  }

  async createPrivatePSK(
    site: string,
    wlanId: string,
    psk: PrivatePSKCreateInput,
  ): Promise<PrivatePSK[]> {
    const paths = [
      `/proxy/network/api/s/${site}/rest/wlanconf/${wlanId}`,
      `/api/s/${site}/rest/wlanconf/${wlanId}`,
    ];

    // Fetch current PPSKs
    let currentPpsks: any[] = [];
    for (const path of paths) {
      try {
        const res = await this.client.get(path);
        const raw = (res.data?.data?.[0] ?? res.data) as any;
        currentPpsks = (raw.private_preshared_keys ?? []) as any[];
        break;
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        )
          throw e;
      }
    }

    const newPsk: Record<string, unknown> = {
      key: psk.key,
      name: psk.name,
    };
    if (psk.mac) newPsk.mac = psk.mac;
    if (psk.vlanId !== undefined) newPsk.vlan_id = psk.vlanId;
    currentPpsks.push(newPsk);

    // Update WLAN with new PPSKs
    for (const path of paths) {
      try {
        const res = await this.client.put(path, {
          private_preshared_keys: currentPpsks,
          private_preshared_keys_enabled: true,
        });
        const raw = (res.data?.data?.[0] ?? res.data) as any;
        return UnifiClient.parsePPSKs(raw.private_preshared_keys ?? []);
      } catch (e) {
        if (!axios.isAxiosError(e)) throw e;
        if (
          e.response &&
          e.response.status !== 404 &&
          e.response.status !== 401
        ) {
          throw new Error(
            `Failed to create PPSK: ${e.response.status} ${JSON.stringify(e.response.data)}`,
          );
        }
      }
    }

    throw new Error("Could not create PPSK from any known path");
  }
}
