import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import type { IHyperionConfig, IHyperionRawData, IHyperionSnapshot, THyperionTransport } from './hyperion.types.js';
import {
  hyperionDefaultJsonPort,
  hyperionDefaultOrigin,
  hyperionDefaultPriority,
  hyperionDefaultTimeoutMs,
  hyperionDefaultWebPort,
} from './hyperion.types.js';

export class HyperionConfigFlow implements IConfigFlow<IHyperionConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IHyperionConfig>> {
    void contextArg;
    return {
      kind: 'form',
      title: 'Configure Hyperion',
      description: 'Configure a local Hyperion JSON API endpoint. SSDP candidates use the JSON TCP server by default; manual HTTP/WebSocket endpoints can be selected explicitly.',
      fields: [
        { name: 'host', label: 'Host', type: 'text' },
        { name: 'port', label: 'Port for selected transport', type: 'number' },
        { name: 'transport', label: 'Transport', type: 'select', options: [
          { label: 'TCP JSON server', value: 'tcp' },
          { label: 'HTTP JSON-RPC', value: 'http' },
          { label: 'WebSocket JSON-RPC', value: 'websocket' },
        ] },
        { name: 'ssl', label: 'Use TLS for HTTP/WebSocket', type: 'boolean' },
        { name: 'token', label: 'Authorization token', type: 'password' },
        { name: 'priority', label: 'Input priority', type: 'number' },
        { name: 'origin', label: 'Origin label', type: 'text' },
        { name: 'name', label: 'Name', type: 'text' },
      ],
      submit: async (valuesArg) => this.submit(candidateArg, valuesArg),
    };
  }

  private async submit(candidateArg: IDiscoveryCandidate, valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<IHyperionConfig>> {
    const metadata = candidateArg.metadata || {};
    const parsed = parseEndpoint(this.stringValue(valuesArg.host) || this.stringValue(metadata.url));
    const snapshot = snapshotValue(metadata.snapshot);
    const rawData = rawDataValue(metadata.rawData);
    const transport = this.transportValue(valuesArg.transport) || this.transportValue(metadata.transport) || parsed?.transport || 'tcp';
    const host = parsed?.host || this.stringValue(valuesArg.host) || candidateArg.host || snapshot?.device.host;
    const ssl = this.booleanValue(valuesArg.ssl) ?? this.booleanMetadata(metadata, 'ssl') ?? parsed?.ssl ?? snapshot?.device.ssl ?? false;
    const jsonPort = this.numberValue(metadata.jsonPort) || snapshot?.device.jsonPort || (transport === 'tcp' ? candidateArg.port || parsed?.port : undefined) || hyperionDefaultJsonPort;
    const webPort = this.numberValue(metadata.webPort) || snapshot?.device.webPort || (transport === 'http' || transport === 'websocket' ? candidateArg.port || parsed?.port : undefined) || hyperionDefaultWebPort;
    const port = this.numberValue(valuesArg.port) || parsed?.port || candidateArg.port || (transport === 'tcp' ? jsonPort : webPort);
    const token = this.stringValue(valuesArg.token) || this.stringMetadata(metadata, 'token');
    const hasOfflineData = Boolean(snapshot || rawData || metadata.client);

    if (!host && !hasOfflineData) {
      return { kind: 'error', title: 'Hyperion setup failed', error: 'Hyperion host, URL, injected client, snapshot, or raw data is required.' };
    }
    if (!this.validPort(port)) {
      return { kind: 'error', title: 'Hyperion setup failed', error: 'Hyperion port must be between 1 and 65535.' };
    }
    if (transport !== 'tcp' && transport !== 'http' && transport !== 'websocket') {
      return { kind: 'error', title: 'Hyperion setup failed', error: 'Hyperion transport must be tcp, http, or websocket.' };
    }

    const config: IHyperionConfig = {
      host,
      port,
      jsonPort,
      webPort,
      ssl,
      transport,
      token,
      timeoutMs: hyperionDefaultTimeoutMs,
      priority: this.numberValue(valuesArg.priority) || this.numberMetadata(metadata, 'priority') || hyperionDefaultPriority,
      origin: this.stringValue(valuesArg.origin) || this.stringMetadata(metadata, 'origin') || hyperionDefaultOrigin,
      effectHideList: this.stringArray(metadata.effectHideList),
      name: this.stringValue(valuesArg.name) || candidateArg.name || snapshot?.device.name || this.stringMetadata(metadata, 'name'),
      manufacturer: candidateArg.manufacturer || snapshot?.device.manufacturer || 'Hyperion',
      model: candidateArg.model || snapshot?.device.model || 'Hyperion-NG',
      uniqueId: candidateArg.id || snapshot?.device.id || (host ? `${host}:${port}` : undefined),
      snapshot,
      rawData,
      client: metadata.client as IHyperionConfig['client'],
      commandExecutor: metadata.commandExecutor as IHyperionConfig['commandExecutor'],
    };

    return { kind: 'done', title: 'Hyperion configured', config };
  }

  private stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim().replace(/\/$/, '') : 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', 'on', '1'].includes(normalized)) {
        return true;
      }
      if (['false', 'no', 'off', '0'].includes(normalized)) {
        return false;
      }
    }
    return undefined;
  }

  private transportValue(valueArg: unknown): THyperionTransport | undefined {
    return valueArg === 'tcp' || valueArg === 'http' || valueArg === 'websocket' ? valueArg : undefined;
  }

  private stringMetadata(metadataArg: Record<string, unknown>, keyArg: string): string | undefined {
    return this.stringValue(metadataArg[keyArg]);
  }

  private numberMetadata(metadataArg: Record<string, unknown>, keyArg: string): number | undefined {
    return this.numberValue(metadataArg[keyArg]);
  }

  private booleanMetadata(metadataArg: Record<string, unknown>, keyArg: string): boolean | undefined {
    return this.booleanValue(metadataArg[keyArg]);
  }

  private stringArray(valueArg: unknown): string[] | undefined {
    return Array.isArray(valueArg) ? valueArg.filter((itemArg): itemArg is string => typeof itemArg === 'string') : undefined;
  }

  private validPort(valueArg: number): boolean {
    return Number.isInteger(valueArg) && valueArg > 0 && valueArg <= 65535;
  }
}

const parseEndpoint = (valueArg: string | undefined): { host: string; port?: number; ssl: boolean; transport: THyperionTransport } | undefined => {
  if (!valueArg || !/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
    return undefined;
  }
  try {
    const url = new URL(valueArg);
    const protocol = url.protocol.replace(':', '').toLowerCase();
    const transport: THyperionTransport = protocol === 'ws' || protocol === 'wss' ? 'websocket' : protocol === 'http' || protocol === 'https' ? 'http' : 'tcp';
    return { host: url.hostname, port: url.port ? Number(url.port) : undefined, ssl: protocol === 'https' || protocol === 'wss', transport };
  } catch {
    return undefined;
  }
};

const snapshotValue = (valueArg: unknown): IHyperionSnapshot | undefined => {
  return valueArg && typeof valueArg === 'object' && 'device' in valueArg && 'instances' in valueArg ? valueArg as IHyperionSnapshot : undefined;
};

const rawDataValue = (valueArg: unknown): IHyperionRawData | undefined => {
  return valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as IHyperionRawData : undefined;
};
