import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import { airosDefaultHttpPort, airosDefaultHttpsPort, airosDefaultName, airosDefaultSsl, airosDefaultTimeoutMs, airosDefaultUsername, airosDefaultVerifySsl } from './airos.constants.js';
import type { IAirosConfig, IAirosFirmwareStatus, IAirosRawStatus, IAirosSnapshot } from './airos.types.js';

export class AirosConfigFlow implements IConfigFlow<IAirosConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IAirosConfig>> {
    void contextArg;
    const metadata = candidateArg.metadata || {};
    const snapshot = metadata.snapshot as IAirosSnapshot | undefined;
    const rawStatus = metadata.rawStatus as IAirosRawStatus | undefined;
    const ssl = this.booleanValue(metadata.ssl) ?? snapshot?.device.ssl ?? airosDefaultSsl;
    const verifySsl = this.booleanValue(metadata.verifySsl ?? metadata.verify_ssl) ?? snapshot?.device.verifySsl ?? airosDefaultVerifySsl;
    const port = candidateArg.port || snapshot?.device.port || (ssl ? airosDefaultHttpsPort : airosDefaultHttpPort);
    const host = candidateArg.host || this.stringValue(metadata.host) || snapshot?.device.host || '';
    const username = this.stringValue(metadata.username) || airosDefaultUsername;

    return {
      kind: 'form',
      title: candidateArg.source === 'dhcp' || candidateArg.source === 'custom' ? 'Confirm Ubiquiti airOS device' : 'Connect Ubiquiti airOS device',
      description: 'Provide local airOS web credentials. Native HTTP(S) is used for snapshots, reboot, and firmware update actions unless an injected client/executor is configured.',
      fields: [
        { name: 'host', label: host ? `Host (${host})` : 'Host', type: 'text', required: !snapshot && !rawStatus },
        { name: 'username', label: `Username (${username})`, type: 'text' },
        { name: 'password', label: 'Password', type: 'password', required: !snapshot && !rawStatus },
        { name: 'ssl', label: `Use HTTPS (${ssl ? 'yes' : 'no'})`, type: 'boolean' },
        { name: 'verifySsl', label: `Verify SSL certificate (${verifySsl ? 'yes' : 'no'})`, type: 'boolean' },
        { name: 'nativeHttpEnabled', label: 'Enable native local airOS web API', type: 'boolean' },
        { name: 'name', label: candidateArg.name ? `Name (${candidateArg.name})` : 'Name', type: 'text' },
        { name: 'port', label: `Port (${port})`, type: 'number' },
        { name: 'timeoutMs', label: `Timeout ms (${airosDefaultTimeoutMs})`, type: 'number' },
        { name: 'snapshotJson', label: 'Snapshot JSON', type: 'text' },
        { name: 'rawStatusJson', label: 'Raw status.cgi JSON', type: 'text' },
        { name: 'firmwareJson', label: 'Firmware update JSON', type: 'text' },
      ],
      submit: async (valuesArg) => this.submit(candidateArg, valuesArg),
    };
  }

  private async submit(candidateArg: IDiscoveryCandidate, valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<IAirosConfig>> {
    const metadata = candidateArg.metadata || {};
    const candidateSnapshot = metadata.snapshot as IAirosSnapshot | undefined;
    const candidateRawStatus = metadata.rawStatus as IAirosRawStatus | undefined;
    const snapshot = this.snapshotFromInput(valuesArg.snapshotJson) || candidateSnapshot;
    if (snapshot instanceof Error) {
      return { kind: 'error', title: 'Invalid airOS snapshot', error: snapshot.message };
    }
    const rawStatus = this.objectFromInput<IAirosRawStatus>(valuesArg.rawStatusJson, 'Raw status JSON must be an object.') || candidateRawStatus;
    if (rawStatus instanceof Error) {
      return { kind: 'error', title: 'Invalid airOS raw status', error: rawStatus.message };
    }
    const firmwareStatus = this.objectFromInput<IAirosFirmwareStatus>(valuesArg.firmwareJson, 'Firmware JSON must be an object.') || metadata.firmwareStatus as IAirosFirmwareStatus | undefined;
    if (firmwareStatus instanceof Error) {
      return { kind: 'error', title: 'Invalid airOS firmware data', error: firmwareStatus.message };
    }

    const host = this.stringValue(valuesArg.host) || candidateArg.host || this.stringValue(metadata.host) || snapshot?.device.host;
    if (!host && !snapshot && !rawStatus) {
      return { kind: 'error', title: 'airOS setup failed', error: 'airOS setup requires a host unless a snapshot or raw status JSON is provided.' };
    }

    const password = this.stringValue(valuesArg.password) || this.stringValue(metadata.password);
    if (!password && !snapshot && !rawStatus) {
      return { kind: 'error', title: 'airOS setup failed', error: 'airOS setup requires a password unless a snapshot or raw status JSON is provided.' };
    }

    const ssl = this.booleanValue(valuesArg.ssl) ?? this.booleanValue(metadata.ssl) ?? snapshot?.device.ssl ?? airosDefaultSsl;
    const port = this.numberValue(valuesArg.port) ?? candidateArg.port ?? snapshot?.device.port ?? (ssl ? airosDefaultHttpsPort : airosDefaultHttpPort);
    if (port < 1 || port > 65535) {
      return { kind: 'error', title: 'airOS setup failed', error: 'Port must be between 1 and 65535.' };
    }

    const timeoutMs = this.numberValue(valuesArg.timeoutMs) ?? airosDefaultTimeoutMs;
    if (timeoutMs < 1) {
      return { kind: 'error', title: 'airOS setup failed', error: 'Timeout must be greater than zero.' };
    }

    const config: IAirosConfig = {
      id: candidateArg.id || candidateArg.macAddress || snapshot?.device.id || host,
      host,
      port,
      name: this.stringValue(valuesArg.name) || candidateArg.name || snapshot?.device.name || this.stringValue(rawStatus?.host?.hostname) || airosDefaultName,
      username: this.stringValue(valuesArg.username) || this.stringValue(metadata.username) || airosDefaultUsername,
      password,
      ssl,
      verifySsl: this.booleanValue(valuesArg.verifySsl) ?? this.booleanValue(metadata.verifySsl ?? metadata.verify_ssl) ?? snapshot?.device.verifySsl ?? airosDefaultVerifySsl,
      nativeHttpEnabled: this.booleanValue(valuesArg.nativeHttpEnabled) ?? true,
      timeoutMs,
      manufacturer: candidateArg.manufacturer || snapshot?.device.manufacturer,
      model: candidateArg.model || snapshot?.device.modelName || this.stringValue(rawStatus?.host?.devmodel),
      macAddress: candidateArg.macAddress || snapshot?.device.macAddress,
      snapshot,
      rawStatus,
      firmwareStatus,
      metadata: {
        discoverySource: candidateArg.source,
        discoveryMetadata: metadata,
        nativeHttpImplemented: true,
        configFlowConnectionVerified: false,
      },
    };

    return {
      kind: 'done',
      title: 'Ubiquiti airOS device configured',
      config,
    };
  }

  private snapshotFromInput(valueArg: unknown): IAirosSnapshot | undefined | Error {
    const parsed = this.objectFromInput<IAirosSnapshot>(valueArg, 'Snapshot JSON must be an object.');
    if (!parsed || parsed instanceof Error) {
      return parsed;
    }
    if (!parsed.device || !parsed.host || !parsed.services || !parsed.wireless) {
      return new Error('Snapshot JSON must include device, host, services, and wireless objects.');
    }
    return parsed;
  }

  private objectFromInput<TValue>(valueArg: unknown, errorArg: string): TValue | undefined | Error {
    if (valueArg && typeof valueArg === 'object') {
      return valueArg as TValue;
    }
    const text = this.stringValue(valueArg);
    if (!text) {
      return undefined;
    }
    try {
      const parsed = JSON.parse(text) as unknown;
      if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        return new Error(errorArg);
      }
      return parsed as TValue;
    } catch (errorArg2) {
      return errorArg2 instanceof Error ? errorArg2 : new Error(String(errorArg2));
    }
  }

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

  private numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return Math.round(valueArg);
    }
    if (typeof valueArg === 'string' && valueArg.trim()) {
      const parsed = Number(valueArg);
      return Number.isFinite(parsed) ? Math.round(parsed) : undefined;
    }
    return undefined;
  }

  private booleanValue(valueArg: unknown): boolean | undefined {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'string') {
      const normalized = valueArg.trim().toLowerCase();
      if (normalized === 'true' || normalized === 'yes' || normalized === '1') {
        return true;
      }
      if (normalized === 'false' || normalized === 'no' || normalized === '0') {
        return false;
      }
    }
    return undefined;
  }
}
