import { UnifiMapper } from './unifi.mapper.js';
import type { IUnifiCommand, IUnifiCommandResult, IUnifiConfig, IUnifiEvent, IUnifiSnapshot } from './unifi.types.js';
import { unifiDefaultPort, unifiDefaultSite } from './unifi.types.js';

type TUnifiApiResponse<TData> = {
  meta?: {
    rc?: string;
    msg?: string;
  };
  data?: TData;
};

export class UnifiClient {
  private snapshot?: IUnifiSnapshot;
  private isUnifiOs?: boolean;
  private cookie?: string;
  private csrfToken?: string;
  private eventHandlers = new Set<(eventArg: IUnifiEvent) => void>();

  constructor(private readonly config: IUnifiConfig) {}

  public async getSnapshot(): Promise<IUnifiSnapshot> {
    if (this.config.snapshot || this.config.manualEntries || this.config.clients || this.config.devices || this.config.wlans || this.config.ports) {
      this.snapshot = UnifiMapper.toSnapshot(this.config, this.config.snapshot?.connected ?? true);
      return this.snapshot;
    }

    if (this.canUseHttp()) {
      this.snapshot = await this.fetchSnapshot();
      return this.snapshot;
    }

    this.snapshot = UnifiMapper.toSnapshot(this.config, false);
    return this.snapshot;
  }

  public onEvent(handlerArg: (eventArg: IUnifiEvent) => void): () => void {
    this.eventHandlers.add(handlerArg);
    return () => this.eventHandlers.delete(handlerArg);
  }

  public async sendCommand(commandArg: IUnifiCommand): Promise<IUnifiCommandResult> {
    if (this.config.snapshot || !this.canUseHttp()) {
      const snapshot = this.snapshot || await this.getSnapshot();
      this.applyCommandToSnapshot(snapshot, commandArg);
      this.emit({ type: 'state_changed', data: commandArg, timestamp: Date.now() });
      return { success: true, data: commandArg };
    }

    return {
      success: false,
      error: 'UniFi live write commands are not enabled in this TypeScript port because full controller login/session and MFA handling is incomplete.',
    };
  }

  public async destroy(): Promise<void> {
    this.eventHandlers.clear();
  }

  private async fetchSnapshot(): Promise<IUnifiSnapshot> {
    await this.login();
    const site = this.config.site || unifiDefaultSite;
    const [sites, clients, allClients, devices, wlans, systemInfo] = await Promise.all([
      this.requestData<Record<string, unknown>[]>('/self/sites', { global: true }).catch(() => []),
      this.requestData<Record<string, unknown>[]>('/stat/sta').catch(() => []),
      this.requestData<Record<string, unknown>[]>('/rest/user').catch(() => []),
      this.requestData<Record<string, unknown>[]>('/stat/device').catch(() => []),
      this.requestData<Record<string, unknown>[]>('/rest/wlanconf').catch(() => []),
      this.requestData<Record<string, unknown>[]>('/stat/sysinfo').catch(() => []),
    ]);
    const activeMacs = new Set(clients.map((clientArg) => String(clientArg.mac || '').toLowerCase()).filter(Boolean));
    const mergedClients = [
      ...clients,
      ...allClients.filter((clientArg) => !activeMacs.has(String(clientArg.mac || '').toLowerCase())),
    ];

    return UnifiMapper.toSnapshot({
      ...this.config,
      site,
      controller: {
        id: String(systemInfo[0]?.anonymous_controller_id || this.config.host || 'unifi'),
        name: String(systemInfo[0]?.name || 'UniFi Network'),
        host: this.config.host,
        port: this.config.port || unifiDefaultPort,
        site,
        version: typeof systemInfo[0]?.version === 'string' ? systemInfo[0].version : undefined,
        deviceType: typeof systemInfo[0]?.ubnt_device_type === 'string' ? systemInfo[0].ubnt_device_type : undefined,
        isUnifiOs: this.isUnifiOs,
        connected: true,
      },
      sites: sites.map((siteArg) => ({
        id: this.stringValue(siteArg._id),
        name: this.stringValue(siteArg.name),
        description: this.stringValue(siteArg.desc),
        role: this.stringValue(siteArg.role),
      })),
      clients: mergedClients.map((clientArg) => ({ ...clientArg, mac: String(clientArg.mac || '') })),
      devices: devices.map((deviceArg) => ({ ...deviceArg, mac: String(deviceArg.mac || '') })),
      wlans: wlans.map((wlanArg) => ({ ...wlanArg, name: String(wlanArg.name || wlanArg._id || 'WLAN') })),
    }, true);
  }

  private async login(): Promise<void> {
    if (!this.config.host || !this.config.username || !this.config.password) {
      throw new Error('UniFi host, username, and password are required for controller API access.');
    }

    if (this.isUnifiOs === undefined) {
      this.isUnifiOs = await this.detectUnifiOs();
    }

    const path = this.isUnifiOs ? '/api/auth/login' : '/api/login';
    const response = await this.fetchPath(path, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        username: this.config.username,
        password: this.config.password,
        rememberMe: true,
      }),
    });
    const text = await response.text();
    if (!response.ok) {
      throw new Error(`UniFi login failed with HTTP ${response.status}.`);
    }
    const parsed = text ? JSON.parse(text) as TUnifiApiResponse<unknown> : {};
    if (parsed.meta?.rc === 'error') {
      throw new Error(`UniFi login failed: ${parsed.meta.msg || 'authentication error'}`);
    }
    this.cookie = response.headers.get('set-cookie') || this.cookie;
    this.csrfToken = response.headers.get('x-csrf-token') || this.csrfToken;
  }

  private async detectUnifiOs(): Promise<boolean> {
    const response = await this.fetchPath('', { method: 'GET', redirect: 'manual' }).catch(() => undefined);
    return response?.status === 200;
  }

  private async requestData<TData>(pathArg: string, optionsArg: { global?: boolean } = {}): Promise<TData> {
    const site = this.config.site || unifiDefaultSite;
    const prefix = optionsArg.global
      ? this.isUnifiOs ? '/proxy/network/api' : '/api'
      : this.isUnifiOs ? `/proxy/network/api/s/${encodeURIComponent(site)}` : `/api/s/${encodeURIComponent(site)}`;
    const response = await this.fetchPath(`${prefix}${pathArg}`, {
      method: 'GET',
      headers: this.authHeaders(),
    });
    const text = await response.text();
    if (!response.ok) {
      throw new Error(`UniFi request ${pathArg} failed with HTTP ${response.status}: ${text}`);
    }
    const parsed = text ? JSON.parse(text) as TUnifiApiResponse<TData> : {};
    if (parsed.meta?.rc === 'error') {
      throw new Error(`UniFi request ${pathArg} failed: ${parsed.meta.msg || 'controller error'}`);
    }
    return (parsed.data || []) as TData;
  }

  private fetchPath(pathArg: string, initArg: RequestInit): Promise<Response> {
    return globalThis.fetch(`${this.baseUrl()}${pathArg}`, initArg);
  }

  private authHeaders(): Record<string, string> {
    const headers: Record<string, string> = {};
    if (this.cookie) {
      headers.cookie = this.cookie;
    }
    if (this.csrfToken) {
      headers['x-csrf-token'] = this.csrfToken;
    }
    return headers;
  }

  private baseUrl(): string {
    const protocol = this.config.protocol || 'https';
    const port = this.config.port ?? unifiDefaultPort;
    const defaultPort = protocol === 'https' ? 443 : 80;
    return `${protocol}://${this.config.host}${port === defaultPort ? '' : `:${port}`}`;
  }

  private canUseHttp(): boolean {
    return Boolean(this.config.host && this.config.username && this.config.password);
  }

  private applyCommandToSnapshot(snapshotArg: IUnifiSnapshot, commandArg: IUnifiCommand): void {
    if (commandArg.type === 'blockClient' && commandArg.mac) {
      const client = snapshotArg.clients.find((clientArg) => UnifiMapper.normalizeMac(clientArg.mac) === UnifiMapper.normalizeMac(commandArg.mac));
      if (client && typeof commandArg.block === 'boolean') {
        client.blocked = commandArg.block;
      }
    }
    if (commandArg.type === 'setWlanEnabled' && commandArg.wlanId) {
      const wlan = snapshotArg.wlans.find((wlanArg) => wlanArg.id === commandArg.wlanId || wlanArg._id === commandArg.wlanId || wlanArg.name === commandArg.wlanId);
      if (wlan && typeof commandArg.enabled === 'boolean') {
        wlan.enabled = commandArg.enabled;
      }
    }
    if (commandArg.type === 'setPoePortEnabled' && commandArg.deviceMac && commandArg.portIdx !== undefined) {
      for (const port of snapshotArg.ports) {
        if (UnifiMapper.normalizeMac(port.deviceMac || port.device_mac) === UnifiMapper.normalizeMac(commandArg.deviceMac) && String(port.portIdx ?? port.port_idx ?? port.ifname) === String(commandArg.portIdx)) {
          port.poeMode = commandArg.enabled ? 'auto' : 'off';
          port.poe_mode = port.poeMode;
        }
      }
      for (const device of snapshotArg.devices) {
        for (const port of device.portTable || device.port_table || []) {
          if (UnifiMapper.normalizeMac(device.mac) === UnifiMapper.normalizeMac(commandArg.deviceMac) && String(port.portIdx ?? port.port_idx ?? port.ifname) === String(commandArg.portIdx)) {
            port.poeMode = commandArg.enabled ? 'auto' : 'off';
            port.poe_mode = port.poeMode;
          }
        }
      }
    }
  }

  private emit(eventArg: IUnifiEvent): void {
    for (const handler of this.eventHandlers) {
      handler(eventArg);
    }
  }

  private stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg ? valueArg : undefined;
  }
}
