import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import { ddwrtDefaultHttpPort, ddwrtDefaultHttpsPort, ddwrtDefaultSsl, ddwrtDefaultTimeoutMs, ddwrtDefaultVerifySsl, ddwrtDefaultWirelessOnly, ddwrtDisplayName } from './ddwrt.constants.js';
import { DdwrtMapper } from './ddwrt.mapper.js';
import type { IDdwrtConfig, IDdwrtRawStatusSet, IDdwrtSnapshot, IDdwrtStatusData } from './ddwrt.types.js';

export class DdwrtConfigFlow implements IConfigFlow<IDdwrtConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IDdwrtConfig>> {
    void contextArg;
    const metadata = candidateArg.metadata || {};
    const snapshot = this.snapshotFromValue(metadata.snapshot);
    const rawStatus = this.rawStatusFromValue(metadata.rawStatus);
    const ssl = this.booleanValue(metadata.ssl) ?? snapshot?.router.ssl ?? ddwrtDefaultSsl;
    const verifySsl = this.booleanValue(metadata.verifySsl ?? metadata.verify_ssl) ?? snapshot?.router.verifySsl ?? ddwrtDefaultVerifySsl;
    const wirelessOnly = this.booleanValue(metadata.wirelessOnly ?? metadata.wireless_only) ?? snapshot?.router.wirelessOnly ?? ddwrtDefaultWirelessOnly;
    const port = candidateArg.port || snapshot?.router.port || (ssl ? ddwrtDefaultHttpsPort : ddwrtDefaultHttpPort);
    const host = candidateArg.host || this.stringValue(metadata.host) || snapshot?.router.host || '';
    const username = this.stringValue(metadata.username) || '';
    const hasOfflineData = Boolean(snapshot || rawStatus);

    return {
      kind: 'form',
      title: candidateArg.source === 'manual' ? 'Connect DD-WRT router' : 'Confirm DD-WRT router',
      description: 'Provide local DD-WRT web credentials for live snapshots, or paste captured Status_Wireless/Status_Lan live data for read-only local snapshots.',
      fields: [
        { name: 'host', label: host ? `Host (${host})` : 'Host', type: 'text', required: !hasOfflineData },
        { name: 'username', label: username ? `Username (${username})` : 'Username', type: 'text', required: !hasOfflineData },
        { name: 'password', label: 'Password', type: 'password', required: !hasOfflineData },
        { name: 'ssl', label: `Use HTTPS (${ssl ? 'yes' : 'no'})`, type: 'boolean' },
        { name: 'verifySsl', label: `Verify SSL certificate (${verifySsl ? 'yes' : 'no'})`, type: 'boolean' },
        { name: 'wirelessOnly', label: `Track wireless clients only (${wirelessOnly ? 'yes' : 'no'})`, type: 'boolean' },
        { name: 'nativeHttpEnabled', label: 'Enable native local DD-WRT status HTTP reads', 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 (${ddwrtDefaultTimeoutMs})`, type: 'number' },
        { name: 'snapshotJson', label: 'Snapshot JSON', type: 'text' },
        { name: 'wirelessStatus', label: 'Status_Wireless.live.asp JSON or response', type: 'text' },
        { name: 'lanStatus', label: 'Status_Lan.live.asp JSON or response', type: 'text' },
      ],
      submit: async (valuesArg) => this.submit(candidateArg, valuesArg),
    };
  }

  private async submit(candidateArg: IDiscoveryCandidate, valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<IDdwrtConfig>> {
    const metadata = candidateArg.metadata || {};
    const metadataSnapshot = this.snapshotFromValue(metadata.snapshot);
    const metadataRawStatus = this.rawStatusFromValue(metadata.rawStatus);
    const snapshot = this.snapshotFromInput(valuesArg.snapshotJson) || metadataSnapshot;
    if (snapshot instanceof Error) {
      return { kind: 'error', title: 'Invalid DD-WRT snapshot', error: snapshot.message };
    }

    const wirelessStatus = this.statusFromInput(valuesArg.wirelessStatus, 'Wireless status must be a DD-WRT response string or JSON object.') || metadataRawStatus?.wireless;
    if (wirelessStatus instanceof Error) {
      return { kind: 'error', title: 'Invalid DD-WRT wireless status', error: wirelessStatus.message };
    }
    const lanStatus = this.statusFromInput(valuesArg.lanStatus, 'LAN status must be a DD-WRT response string or JSON object.') || metadataRawStatus?.lan;
    if (lanStatus instanceof Error) {
      return { kind: 'error', title: 'Invalid DD-WRT LAN status', error: lanStatus.message };
    }

    const rawStatus = metadataRawStatus || wirelessStatus || lanStatus ? {
      ...metadataRawStatus,
      wireless: wirelessStatus || metadataRawStatus?.wireless,
      lan: lanStatus || metadataRawStatus?.lan,
    } satisfies IDdwrtRawStatusSet : undefined;
    const hasOfflineData = Boolean(snapshot || rawStatus);
    const host = this.stringValue(valuesArg.host) || candidateArg.host || this.stringValue(metadata.host) || snapshot?.router.host;
    if (!host && !hasOfflineData) {
      return { kind: 'error', title: 'DD-WRT setup failed', error: 'DD-WRT setup requires a host unless a snapshot or live status data is provided.' };
    }

    const username = this.stringValue(valuesArg.username) || this.stringValue(metadata.username);
    const password = this.stringValue(valuesArg.password) || this.stringValue(metadata.password);
    if ((!username || !password) && !hasOfflineData) {
      return { kind: 'error', title: 'DD-WRT setup failed', error: 'DD-WRT setup requires username and password unless a snapshot or live status data is provided.' };
    }

    const ssl = this.booleanValue(valuesArg.ssl) ?? this.booleanValue(metadata.ssl) ?? snapshot?.router.ssl ?? ddwrtDefaultSsl;
    const port = this.numberValue(valuesArg.port) ?? candidateArg.port ?? snapshot?.router.port ?? (ssl ? ddwrtDefaultHttpsPort : ddwrtDefaultHttpPort);
    if (port < 1 || port > 65535) {
      return { kind: 'error', title: 'DD-WRT setup failed', error: 'Port must be between 1 and 65535.' };
    }

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

    const config: IDdwrtConfig = {
      id: candidateArg.id || candidateArg.macAddress || snapshot?.router.id || host,
      host,
      port,
      name: this.stringValue(valuesArg.name) || candidateArg.name || snapshot?.router.name || host || ddwrtDisplayName,
      username,
      password,
      ssl,
      verifySsl: this.booleanValue(valuesArg.verifySsl) ?? this.booleanValue(metadata.verifySsl ?? metadata.verify_ssl) ?? snapshot?.router.verifySsl ?? ddwrtDefaultVerifySsl,
      wirelessOnly: this.booleanValue(valuesArg.wirelessOnly) ?? this.booleanValue(metadata.wirelessOnly ?? metadata.wireless_only) ?? snapshot?.router.wirelessOnly ?? ddwrtDefaultWirelessOnly,
      nativeHttpEnabled: this.booleanValue(valuesArg.nativeHttpEnabled) ?? true,
      timeoutMs,
      manufacturer: candidateArg.manufacturer || snapshot?.router.manufacturer,
      model: candidateArg.model || snapshot?.router.model,
      macAddress: candidateArg.macAddress || snapshot?.router.macAddress,
      snapshot,
      rawStatus,
      metadata: {
        discoverySource: candidateArg.source,
        discoveryMetadata: metadata,
        nativeHttpImplemented: true,
        configFlowConnectionVerified: false,
      },
    };

    return {
      kind: 'done',
      title: 'DD-WRT router configured',
      config,
    };
  }

  private snapshotFromInput(valueArg: unknown): IDdwrtSnapshot | undefined | Error {
    const parsed = this.objectFromInput<IDdwrtSnapshot>(valueArg, 'Snapshot JSON must be an object.');
    if (!parsed || parsed instanceof Error) {
      return parsed;
    }
    if (!parsed.router || !Array.isArray(parsed.clients) || !parsed.sensors) {
      return new Error('Snapshot JSON must include router, clients, and sensors.');
    }
    return parsed;
  }

  private statusFromInput(valueArg: unknown, errorArg: string): IDdwrtStatusData | undefined | Error {
    if (!valueArg) {
      return undefined;
    }
    if (typeof valueArg === 'object' && !Array.isArray(valueArg)) {
      return DdwrtMapper.toStatusData(valueArg) || new Error(errorArg);
    }
    const text = this.stringValue(valueArg);
    if (!text) {
      return undefined;
    }
    const parsedResponse = DdwrtMapper.toStatusData(text);
    if (parsedResponse) {
      return parsedResponse;
    }
    const parsedObject = this.objectFromInput<IDdwrtStatusData>(text, errorArg);
    if (!parsedObject || parsedObject instanceof Error) {
      return parsedObject;
    }
    return DdwrtMapper.toStatusData(parsedObject) || new Error(errorArg);
  }

  private objectFromInput<TValue>(valueArg: unknown, errorArg: string): TValue | undefined | Error {
    if (valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg)) {
      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 snapshotFromValue(valueArg: unknown): IDdwrtSnapshot | undefined {
    return valueArg && typeof valueArg === 'object' && 'router' in valueArg && 'clients' in valueArg && 'sensors' in valueArg ? valueArg as IDdwrtSnapshot : undefined;
  }

  private rawStatusFromValue(valueArg: unknown): IDdwrtRawStatusSet | undefined {
    return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as IDdwrtRawStatusSet : undefined;
  }

  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 (['true', 'yes', '1', 'on'].includes(normalized)) {
        return true;
      }
      if (['false', 'no', '0', 'off'].includes(normalized)) {
        return false;
      }
    }
    return undefined;
  }
}
