import * as plugins from '../../plugins.js';
import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import type { IDaikinConfig, IDaikinRawData, IDaikinSnapshot, IDaikinValueMap, TDaikinApiVariant } from './daikin.types.js';
import { daikinDefaultHttpPort, daikinDefaultHttpsPort, daikinDefaultTimeoutMs, daikinSkyFiPort } from './daikin.types.js';

export class DaikinConfigFlow implements IConfigFlow<IDaikinConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IDaikinConfig>> {
    void contextArg;
    return {
      kind: 'form',
      title: 'Configure Daikin AC',
      description: 'Configure a local Daikin endpoint, or use snapshot/manual data from the discovery candidate. Use either API key or SkyFi password, not both.',
      fields: [
        { name: 'host', label: 'Host', type: 'text' },
        { name: 'port', label: 'Port', type: 'number' },
        { name: 'apiVariant', label: 'API variant', type: 'select', options: [
          { label: 'Auto / BRP069 local HTTP', value: 'auto' },
          { label: 'BRP069 local HTTP', value: 'brp069' },
          { label: 'BRP072C HTTPS with API key', value: 'brp072c' },
          { label: 'AirBase / skyfi path prefix', value: 'airbase' },
          { label: 'SkyFi password API', value: 'skyfi' },
          { label: 'BRP084 dsiot read-only', value: 'brp084' },
        ] },
        { name: 'ssl', label: 'Use HTTPS', type: 'boolean' },
        { name: 'verifySsl', label: 'Verify TLS certificate', type: 'boolean' },
        { name: 'apiKey', label: 'API key', type: 'password' },
        { name: 'uuid', label: 'Terminal UUID', type: 'text' },
        { name: 'password', label: 'SkyFi password', type: 'password' },
        { name: 'name', label: 'Name', type: 'text' },
      ],
      submit: async (valuesArg) => this.submit(candidateArg, valuesArg),
    };
  }

  private async submit(candidateArg: IDiscoveryCandidate, valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<IDaikinConfig>> {
    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 apiKey = this.stringValue(valuesArg.apiKey) || this.stringMetadata(metadata, 'apiKey');
    const password = this.stringValue(valuesArg.password) || this.stringMetadata(metadata, 'password');
    const apiVariant = this.apiVariant(this.stringValue(valuesArg.apiVariant) || this.stringMetadata(metadata, 'apiVariant'), apiKey, password);
    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 ?? apiVariant === 'brp072c';
    const port = this.numberValue(valuesArg.port) || candidateArg.port || parsed?.port || snapshot?.device.port || this.defaultPort(apiVariant, ssl);
    const hasManualData = Boolean(snapshot || rawData || metadata.values || metadata.client);

    if (apiKey && password) {
      return { kind: 'error', title: 'Daikin setup failed', error: 'Invalid authentication: use either API key or password, not both.' };
    }
    if (!host && !hasManualData) {
      return { kind: 'error', title: 'Daikin setup failed', error: 'Daikin host, injected client, snapshot, or manual data is required.' };
    }
    if (!this.validPort(port)) {
      return { kind: 'error', title: 'Daikin setup failed', error: 'Daikin port must be between 1 and 65535.' };
    }

    const config: IDaikinConfig = {
      host,
      port,
      ssl,
      verifySsl: this.booleanValue(valuesArg.verifySsl) ?? this.booleanMetadata(metadata, 'verifySsl') ?? false,
      apiKey,
      uuid: this.stringValue(valuesArg.uuid) || this.stringMetadata(metadata, 'uuid') || (apiKey ? plugins.crypto.randomUUID().replace(/-/g, '') : undefined),
      password,
      apiVariant,
      timeoutMs: daikinDefaultTimeoutMs,
      name: this.stringValue(valuesArg.name) || candidateArg.name || snapshot?.device.name || this.stringMetadata(metadata, 'name'),
      manufacturer: candidateArg.manufacturer || snapshot?.device.manufacturer || 'Daikin',
      model: candidateArg.model || snapshot?.device.model,
      uniqueId: candidateArg.id || snapshot?.device.id || snapshot?.device.macAddress || (host ? `${host}:${port}` : undefined),
      macAddress: candidateArg.macAddress || snapshot?.device.macAddress,
      snapshot,
      rawData,
      values: valueMapValue(metadata.values),
      client: metadata.client as IDaikinConfig['client'],
      commandExecutor: metadata.commandExecutor as IDaikinConfig['commandExecutor'],
    };

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

  private apiVariant(valueArg: string | undefined, apiKeyArg: string | undefined, passwordArg: string | undefined): TDaikinApiVariant {
    if (passwordArg) {
      return 'skyfi';
    }
    if (apiKeyArg) {
      return 'brp072c';
    }
    const allowed: TDaikinApiVariant[] = ['auto', 'brp069', 'brp072c', 'airbase', 'skyfi', 'brp084'];
    return allowed.includes(valueArg as TDaikinApiVariant) ? valueArg as TDaikinApiVariant : 'auto';
  }

  private defaultPort(apiVariantArg: TDaikinApiVariant, sslArg: boolean): number {
    if (apiVariantArg === 'skyfi') {
      return daikinSkyFiPort;
    }
    return sslArg ? daikinDefaultHttpsPort : daikinDefaultHttpPort;
  }

  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 stringMetadata(metadataArg: Record<string, unknown>, keyArg: string): string | undefined {
    return this.stringValue(metadataArg[keyArg]);
  }

  private booleanMetadata(metadataArg: Record<string, unknown>, keyArg: string): boolean | undefined {
    const value = metadataArg[keyArg];
    return typeof value === 'boolean' ? value : 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 } | undefined => {
  if (!valueArg || !/^[a-z][a-z0-9+.-]*:\/\//i.test(valueArg)) {
    return undefined;
  }
  try {
    const url = new URL(valueArg);
    return { host: url.hostname, port: url.port ? Number(url.port) : undefined, ssl: url.protocol === 'https:' };
  } catch {
    return undefined;
  }
};

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

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

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