import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import { ciscoIosDefaultArpCommand, ciscoIosDefaultName, ciscoIosDefaultPresentAgeMinutes, ciscoIosDefaultSshPort, ciscoIosDefaultTerminalLengthCommand, ciscoIosDefaultUsername, ciscoIosDisplayName, ciscoIosManufacturer } from './cisco_ios.constants.js';
import type { ICiscoIosConfig, ICiscoIosSnapshot } from './cisco_ios.types.js';

export class CiscoIosConfigFlow implements IConfigFlow<ICiscoIosConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<ICiscoIosConfig>> {
    void contextArg;
    const metadata = candidateArg.metadata || {};
    const snapshot = metadata.snapshot as ICiscoIosSnapshot | undefined;
    const hasStaticData = Boolean(snapshot || rawTextValue(metadata.arpOutput) || rawTextValue(metadata.rawArpOutput));
    const host = candidateArg.host || stringValue(metadata.host) || snapshot?.device.host || '';
    const username = stringValue(metadata.username) || snapshot?.device.username || ciscoIosDefaultUsername;
    const port = candidateArg.port || snapshot?.device.port || ciscoIosDefaultSshPort;

    return {
      kind: 'form',
      title: candidateArg.source === 'manual' ? 'Connect Cisco IOS router' : 'Confirm Cisco IOS router',
      description: 'Provide Cisco IOS SSH details or paste show ip arp/snapshot data. This native port does not open SSH itself; live CLI reads require an injected client, executor, or transport.',
      fields: [
        { name: 'host', label: host ? `Host (${host})` : 'Host', type: 'text', required: !hasStaticData },
        { name: 'username', label: `Username (${username})`, type: 'text', required: !hasStaticData },
        { name: 'password', label: 'Password', type: 'password' },
        { name: 'port', label: `SSH port (${port})`, type: 'number' },
        { name: 'name', label: candidateArg.name ? `Name (${candidateArg.name})` : 'Name', type: 'text' },
        { name: 'presentAgeMinutes', label: `Presence age threshold minutes (${ciscoIosDefaultPresentAgeMinutes})`, type: 'number' },
        { name: 'terminalLengthCommand', label: `Terminal paging command (${ciscoIosDefaultTerminalLengthCommand})`, type: 'text' },
        { name: 'arpCommand', label: `ARP command (${ciscoIosDefaultArpCommand})`, type: 'text' },
        { name: 'arpOutput', label: 'show ip arp output', type: 'text' },
        { name: 'snapshotJson', label: 'Cisco IOS snapshot JSON', type: 'text' },
      ],
      submit: async (valuesArg) => this.submit(candidateArg, valuesArg),
    };
  }

  private async submit(candidateArg: IDiscoveryCandidate, valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<ICiscoIosConfig>> {
    const metadata = candidateArg.metadata || {};
    const candidateSnapshot = metadata.snapshot as ICiscoIosSnapshot | undefined;
    const snapshot = this.snapshotFromInput(valuesArg.snapshotJson) || candidateSnapshot;
    if (snapshot instanceof Error) {
      return { kind: 'error', title: 'Invalid Cisco IOS snapshot', error: snapshot.message };
    }
    const arpOutput = rawTextValue(valuesArg.arpOutput) || rawTextValue(metadata.arpOutput) || rawTextValue(metadata.rawArpOutput);
    const host = stringValue(valuesArg.host) || candidateArg.host || stringValue(metadata.host) || snapshot?.device.host;
    if (!host && !snapshot && !arpOutput) {
      return { kind: 'error', title: 'Cisco IOS setup failed', error: 'Cisco IOS setup requires a host unless a snapshot or show ip arp output is provided.' };
    }

    const username = stringValue(valuesArg.username) || stringValue(metadata.username) || snapshot?.device.username;
    if (host && !username && !snapshot && !arpOutput) {
      return { kind: 'error', title: 'Cisco IOS setup failed', error: 'Cisco IOS SSH setup requires a username unless a snapshot or show ip arp output is provided.' };
    }

    const port = numberValue(valuesArg.port) ?? candidateArg.port ?? snapshot?.device.port ?? ciscoIosDefaultSshPort;
    if (port < 1 || port > 65535) {
      return { kind: 'error', title: 'Cisco IOS setup failed', error: 'Port must be between 1 and 65535.' };
    }

    const presentAgeMinutes = numberValue(valuesArg.presentAgeMinutes) ?? ciscoIosDefaultPresentAgeMinutes;
    if (presentAgeMinutes <= 0) {
      return { kind: 'error', title: 'Cisco IOS setup failed', error: 'Presence age threshold must be greater than zero.' };
    }

    const config: ICiscoIosConfig = {
      id: candidateArg.id || candidateArg.macAddress || snapshot?.device.id || host,
      host,
      port,
      name: stringValue(valuesArg.name) || candidateArg.name || snapshot?.device.name || host || ciscoIosDefaultName,
      username: username || ciscoIosDefaultUsername,
      password: stringValue(valuesArg.password) || stringValue(metadata.password),
      manufacturer: candidateArg.manufacturer || snapshot?.device.manufacturer || ciscoIosManufacturer,
      model: candidateArg.model || snapshot?.device.modelName || ciscoIosDisplayName,
      macAddress: candidateArg.macAddress || snapshot?.device.macAddress,
      presentAgeMinutes,
      terminalLengthCommand: stringValue(valuesArg.terminalLengthCommand) || ciscoIosDefaultTerminalLengthCommand,
      arpCommand: stringValue(valuesArg.arpCommand) || ciscoIosDefaultArpCommand,
      snapshot,
      arpOutput,
      metadata: {
        discoverySource: candidateArg.source,
        discoveryMetadata: metadata,
        nativeSshImplemented: false,
        readOnlyWithoutInjectedTransport: true,
      },
    };

    return {
      kind: 'done',
      title: 'Cisco IOS router configured',
      config,
    };
  }

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

const objectFromInput = <TValue>(valueArg: unknown, errorArg: string): TValue | undefined | Error => {
  if (valueArg && typeof valueArg === 'object') {
    return valueArg as TValue;
  }
  const textValue = stringValue(valueArg);
  if (!textValue) {
    return undefined;
  }
  try {
    const parsed = JSON.parse(textValue) 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));
  }
};

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const rawTextValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg : undefined;

const 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;
};
