import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import { FroniusClient } from './fronius.classes.client.js';
import type { IFroniusConfig, IFroniusRawData, IFroniusSnapshot, TFroniusApiVersion, TFroniusProtocol } from './fronius.types.js';
import { froniusDefaultHttpPort, froniusDefaultHttpsPort, froniusDefaultTimeoutMs, froniusDisplayName, froniusManufacturer } from './fronius.types.js';

export class FroniusConfigFlow implements IConfigFlow<IFroniusConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IFroniusConfig>> {
    void contextArg;
    return {
      kind: 'form',
      title: 'Configure Fronius',
      description: 'Configure a local Fronius Solar API endpoint. The runtime reads local snapshots from the device and keeps write calls delegated to an injected client or command executor.',
      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: 'HTTP', value: 'http' }, { label: 'HTTPS', value: 'https' }] },
        { name: 'apiVersion', label: 'Solar API version', type: 'select', required: false, options: [{ label: 'Auto', value: 'auto' }, { label: 'v1', value: 'v1' }, { label: 'v0', value: 'v0' }] },
        { 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<IFroniusConfig>> {
    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.url) || this.stringValue(metadata.host), this.protocolValue(valuesArg.protocol || metadata.protocol));
    const protocol = parsed?.protocol || this.protocolValue(valuesArg.protocol || metadata.protocol) || snapshot?.protocol || 'http';
    const host = parsed?.host || this.stringValue(valuesArg.host) || candidateArg.host || snapshot?.host || this.stringValue(metadata.host);
    const port = this.numberValue(valuesArg.port) || parsed?.port || candidateArg.port || snapshot?.port || this.numberValue(metadata.port) || (protocol === 'https' ? froniusDefaultHttpsPort : froniusDefaultHttpPort);
    const hasManualData = Boolean(snapshot || rawData || metadata.client);

    if (!host && !hasManualData) {
      return { kind: 'error', title: 'Fronius setup failed', error: 'Fronius host, injected client, snapshot, or raw Solar API data is required.' };
    }
    if (!this.validPort(port)) {
      return { kind: 'error', title: 'Fronius setup failed', error: 'Fronius port must be between 1 and 65535.' };
    }

    const config: IFroniusConfig = {
      host,
      port,
      protocol,
      apiVersion: this.apiVersionValue(valuesArg.apiVersion || metadata.apiVersion || snapshot?.apiVersion),
      timeoutMs: this.numberValue(metadata.timeoutMs) || froniusDefaultTimeoutMs,
      verifyTls: metadata.verifyTls === true,
      name: this.stringValue(valuesArg.name) || candidateArg.name || snapshot?.system.name || this.stringValue(metadata.name) || froniusDisplayName,
      uniqueId: candidateArg.id || snapshot?.system.uniqueId || this.stringValue(metadata.uniqueId),
      serialNumber: candidateArg.serialNumber || snapshot?.system.serialNumber || this.stringValue(metadata.serialNumber),
      isLogger: typeof metadata.isLogger === 'boolean' ? metadata.isLogger : snapshot?.loggerInfo ? true : undefined,
      snapshot,
      rawData,
      client: metadata.client as IFroniusConfig['client'],
      commandExecutor: metadata.commandExecutor as IFroniusConfig['commandExecutor'],
    };

    if (host && !hasManualData) {
      const snapshotResult = await new FroniusClient(config).getSnapshot(true);
      if (!snapshotResult.online || snapshotResult.error) {
        return { kind: 'error', title: 'Fronius setup failed', error: snapshotResult.error || 'Fronius Solar API did not return a usable local snapshot.' };
      }
      if (!snapshotResult.loggerInfo && !snapshotResult.inverters.length) {
        return { kind: 'error', title: 'Fronius setup failed', error: 'Fronius Solar API did not return logger or inverter identity data.' };
      }
      config.name = this.stringValue(valuesArg.name) || candidateArg.name || snapshotResult.system.name;
      config.uniqueId = snapshotResult.loggerInfo?.uniqueIdentifier || snapshotResult.inverters[0]?.uniqueId || snapshotResult.system.uniqueId;
      config.serialNumber = snapshotResult.system.serialNumber || snapshotResult.inverters[0]?.serialNumber;
      config.isLogger = Boolean(snapshotResult.loggerInfo);
    }

    config.manufacturer = froniusManufacturer;
    return { kind: 'done', title: 'Fronius 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): TFroniusProtocol | undefined {
    return valueArg === 'https' || valueArg === 'http' ? valueArg : undefined;
  }

  private apiVersionValue(valueArg: unknown): TFroniusApiVersion {
    return valueArg === 'v0' || valueArg === 'v1' ? valueArg : 'auto';
  }

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

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

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

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