import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import { FreeboxClient } from './freebox.classes.client.js';
import type { IFreeboxConfig, IFreeboxRawData, IFreeboxSnapshot, TFreeboxProtocol } from './freebox.types.js';
import { freeboxDefaultApiBaseUrl, freeboxDefaultApiVersion, freeboxDefaultPort, freeboxDefaultTimeoutMs, freeboxDisplayName, freeboxManufacturer } from './freebox.types.js';

export class FreeboxConfigFlow implements IConfigFlow<IFreeboxConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IFreeboxConfig>> {
    void contextArg;
    return {
      kind: 'form',
      title: 'Configure Freebox',
      description: 'Configure a local Freebox API endpoint. Authenticated polling requires an app token or session token from Freebox app authorization; static snapshots and injected clients can be used without a host token.',
      fields: [
        { name: 'host', label: 'Host or URL', type: 'text', required: false },
        { name: 'port', label: 'Port', type: 'number', required: false },
        { name: 'protocol', label: 'Protocol', type: 'select', required: false, options: [{ label: 'HTTPS', value: 'https' }, { label: 'HTTP', value: 'http' }] },
        { name: 'apiVersion', label: 'Freebox API version', type: 'text', required: false },
        { name: 'appToken', label: 'App token', type: 'password', required: false },
        { name: 'sessionToken', label: 'Session token', type: 'password', required: false },
        { name: 'name', label: 'Name', type: 'text', required: false },
      ],
      submit: async (valuesArg) => this.submit(candidateArg, valuesArg),
    };
  }

  private async submit(candidateArg: IDiscoveryCandidate, valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<IFreeboxConfig>> {
    const metadata = candidateArg.metadata || {};
    const snapshot = snapshotValue(metadata.snapshot);
    const rawData = rawDataValue(metadata.rawData);
    const parsed = parseEndpoint(this.stringValue(valuesArg.host) || candidateArg.host || this.stringValue(metadata.apiDomain) || this.stringValue(metadata.api_domain) || this.stringValue(metadata.host), this.protocolValue(valuesArg.protocol || metadata.protocol));
    const protocol = parsed?.protocol || this.protocolValue(valuesArg.protocol || metadata.protocol) || snapshot?.router.protocol || 'https';
    const host = parsed?.host || this.stringValue(valuesArg.host) || candidateArg.host || snapshot?.router.host || this.stringValue(metadata.apiDomain) || this.stringValue(metadata.api_domain) || this.stringValue(metadata.host);
    const port = this.numberValue(valuesArg.port) || parsed?.port || candidateArg.port || snapshot?.router.port || this.numberValue(metadata.https_port) || this.numberValue(metadata.port) || freeboxDefaultPort;
    const appToken = this.stringValue(valuesArg.appToken) || this.stringValue(metadata.appToken) || this.stringValue(metadata.token);
    const sessionToken = this.stringValue(valuesArg.sessionToken) || this.stringValue(metadata.sessionToken);
    const hasManualData = Boolean(snapshot || rawData || metadata.client);

    if (!host && !hasManualData) {
      return { kind: 'error', title: 'Freebox setup failed', error: 'Freebox host, injected client, snapshot, or raw local API data is required.' };
    }
    if (!this.validPort(port)) {
      return { kind: 'error', title: 'Freebox setup failed', error: 'Freebox port must be between 1 and 65535.' };
    }
    if (host && !hasManualData && !appToken && !sessionToken) {
      return { kind: 'error', title: 'Freebox setup failed', error: 'Freebox appToken/token or sessionToken is required before authenticated local API polling can be validated.' };
    }

    const config: IFreeboxConfig = {
      host,
      port,
      protocol,
      apiVersion: normalizeApiVersion(this.stringValue(valuesArg.apiVersion) || this.stringValue(metadata.apiVersion) || this.stringValue(metadata.api_version) || snapshot?.router.apiVersion || freeboxDefaultApiVersion),
      apiBaseUrl: this.stringValue(metadata.apiBaseUrl) || this.stringValue(metadata.api_base_url) || snapshot?.router.apiBaseUrl || freeboxDefaultApiBaseUrl,
      timeoutMs: this.numberValue(metadata.timeoutMs) || freeboxDefaultTimeoutMs,
      verifyTls: metadata.verifyTls === true,
      appToken,
      sessionToken,
      name: this.stringValue(valuesArg.name) || candidateArg.name || snapshot?.router.name || this.stringValue(metadata.name) || freeboxDisplayName,
      uniqueId: candidateArg.id || snapshot?.router.macAddress || snapshot?.router.id || this.stringValue(metadata.uid) || this.stringValue(metadata.uniqueId),
      serialNumber: candidateArg.serialNumber || snapshot?.router.serialNumber || this.stringValue(metadata.serialNumber),
      macAddress: candidateArg.macAddress || snapshot?.router.macAddress || this.stringValue(metadata.macAddress),
      snapshot,
      rawData,
      client: metadata.client as IFreeboxConfig['client'],
      commandExecutor: metadata.commandExecutor as IFreeboxConfig['commandExecutor'],
      manufacturer: freeboxManufacturer,
    };

    if (host && !hasManualData) {
      const snapshotResult = await new FreeboxClient(config).getSnapshot(true);
      if (!snapshotResult.online || snapshotResult.error) {
        return { kind: 'error', title: 'Freebox setup failed', error: snapshotResult.error || 'Freebox local API did not return a usable authenticated snapshot.' };
      }
      config.name = this.stringValue(valuesArg.name) || candidateArg.name || snapshotResult.router.name;
      config.uniqueId = snapshotResult.router.macAddress || snapshotResult.router.serialNumber || snapshotResult.router.id;
      config.serialNumber = snapshotResult.router.serialNumber;
      config.macAddress = snapshotResult.router.macAddress;
      config.model = snapshotResult.router.model;
      config.modelId = snapshotResult.router.modelId;
    }

    return { kind: 'done', title: 'Freebox 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 protocolValue(valueArg: unknown): TFreeboxProtocol | undefined {
    return valueArg === 'http' || valueArg === 'https' ? valueArg : undefined;
  }

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

const snapshotValue = (valueArg: unknown): IFreeboxSnapshot | undefined => valueArg && typeof valueArg === 'object' && 'router' in valueArg && 'sensors' in valueArg ? valueArg as IFreeboxSnapshot : undefined;

const rawDataValue = (valueArg: unknown): Partial<IFreeboxRawData> | undefined => valueArg && typeof valueArg === 'object' && !Array.isArray(valueArg) ? valueArg as Partial<IFreeboxRawData> : undefined;

const parseEndpoint = (valueArg: string | undefined, protocolArg: TFreeboxProtocol | undefined): { protocol: TFreeboxProtocol; host: string; port?: number } | undefined => {
  if (!valueArg) {
    return undefined;
  }
  const protocol = protocolArg || 'https';
  try {
    const url = new URL(/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg) ? valueArg : `${protocol}://${valueArg}`);
    return { protocol: url.protocol === 'http:' ? 'http' : 'https', host: url.hostname, port: url.port ? Number(url.port) : undefined };
  } catch {
    return undefined;
  }
};

const normalizeApiVersion = (valueArg: string): string => valueArg.startsWith('v') ? valueArg : `v${valueArg.split('.')[0] || '6'}`;
