import type { IConfigFlow, IConfigFlowContext, IConfigFlowField, IConfigFlowStep, IDiscoveryCandidate, ISimpleLocalSnapshot } from '../../core/index.js';
import type { ILametricConfig, TLametricRawData } from './lametric.types.js';
import { lametricDefaultHttpsPort, lametricDefaultName } from './lametric.types.js';

export class LametricConfigFlow implements IConfigFlow<ILametricConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<ILametricConfig>> {
    void contextArg;
    return {
      kind: 'form',
      title: 'Connect LaMetric',
      description: 'Configure a local LaMetric Device API endpoint. The device API key is required for live local polling and controls.',
      fields: this.fields(),
      submit: async (valuesArg) => this.submit(candidateArg, valuesArg),
    };
  }

  private async submit(candidateArg: IDiscoveryCandidate, valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<ILametricConfig>> {
    const metadata = candidateArg.metadata || {};
    const endpoint = parseEndpoint(stringValue(valuesArg.host) || stringValue(metadata.url) || stringValue(metadata.location));
    const snapshot = snapshotValue(metadata.snapshot);
    const rawData = rawDataValue(metadata.rawData ?? metadata.rawText);
    const host = endpoint?.host || stringValue(valuesArg.host) || candidateArg.host;
    const port = numberValue(valuesArg.port) || endpoint?.port || candidateArg.port || (host ? lametricDefaultHttpsPort : undefined);
    const apiKey = stringValue(valuesArg.apiKey) || stringValue(metadata.apiKey) || stringValue(valuesArg.token) || stringValue(metadata.token);
    const hasOfflineData = Boolean(snapshot || rawData !== undefined || metadata.client || metadata.snapshotProvider);

    if (!host && !hasOfflineData) {
      return { kind: 'error', title: 'LaMetric setup failed', error: 'LaMetric setup requires a host/url, injected client, snapshotProvider, snapshot, or rawData.' };
    }
    if (host && !apiKey && !hasOfflineData) {
      return { kind: 'error', title: 'LaMetric setup failed', error: 'LaMetric live local API setup requires a device API key.' };
    }
    if (port !== undefined && (!Number.isInteger(port) || port <= 0 || port > 65535)) {
      return { kind: 'error', title: 'LaMetric setup failed', error: 'LaMetric port must be between 1 and 65535.' };
    }

    return {
      kind: 'done',
      title: 'LaMetric configured',
      config: {
        host,
        port,
        protocol: stringValue(valuesArg.protocol) === 'http' ? 'http' : 'https',
        apiKey,
        name: stringValue(valuesArg.name) || candidateArg.name || snapshot?.device.name || stringValue(metadata.name) || lametricDefaultName,
        uniqueId: candidateArg.id || snapshot?.device.id || (host ? `${host}:${port || ''}` : undefined),
        timeoutMs: numberValue(valuesArg.timeoutMs) || numberValue(metadata.timeoutMs) || 8000,
        snapshot,
        rawData,
        client: metadata.client as ILametricConfig['client'],
        snapshotProvider: metadata.snapshotProvider as ILametricConfig['snapshotProvider'],
        commandExecutor: metadata.commandExecutor as ILametricConfig['commandExecutor'],
        metadata: cleanAttributes({ ...metadata, client: undefined, snapshotProvider: undefined, commandExecutor: undefined }),
      },
    };
  }

  private fields(): IConfigFlowField[] {
    return [
      { name: 'host', label: 'Host', type: 'text', required: true },
      { name: 'port', label: 'Port', type: 'number' },
      { name: 'protocol', label: 'Protocol', type: 'select', options: [{ label: 'HTTPS', value: 'https' }, { label: 'HTTP', value: 'http' }] },
      { name: 'apiKey', label: 'Device API key', type: 'password', required: true },
      { name: 'name', label: 'Name', type: 'text' },
      { name: 'timeoutMs', label: 'Timeout milliseconds', type: 'number' },
    ];
  }
}

function parseEndpoint(valueArg: string | undefined): { host: string; port?: number } | 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 };
  } catch {
    return undefined;
  }
}

function snapshotValue(valueArg: unknown): ISimpleLocalSnapshot | undefined {
  return valueArg && typeof valueArg === 'object' && 'device' in valueArg && 'entities' in valueArg ? valueArg as ISimpleLocalSnapshot : undefined;
}

function rawDataValue(valueArg: unknown): TLametricRawData | undefined {
  if (valueArg === null || ['string', 'number', 'boolean'].includes(typeof valueArg)) {
    return valueArg as TLametricRawData;
  }
  if (Array.isArray(valueArg) || (valueArg && typeof valueArg === 'object')) {
    return valueArg as TLametricRawData;
  }
  return undefined;
}

function stringValue(valueArg: unknown): string | undefined {
  return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
}

function numberValue(valueArg: unknown): number | undefined {
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return Math.round(valueArg);
  }
  if (typeof valueArg === 'string' && valueArg.trim() && Number.isFinite(Number(valueArg))) {
    return Math.round(Number(valueArg));
  }
  return undefined;
}

function cleanAttributes(attributesArg: Record<string, unknown>): Record<string, unknown> {
  return Object.fromEntries(Object.entries(attributesArg).filter(([, valueArg]) => valueArg !== undefined));
}
