import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import type { IHomekitAccessory, IHomekitControllerConfig, IHomekitControllerSnapshot, IHomekitPairingData } from './homekit_controller.types.js';

const pinFormat = /^(\d{3})-?(\d{2})-?(\d{3})$/;
const insecureCodes = new Set(['00000000', '11111111', '22222222', '33333333', '44444444', '55555555', '66666666', '77777777', '88888888', '99999999', '12345678', '87654321']);

export class HomekitControllerConfigFlow implements IConfigFlow<IHomekitControllerConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IHomekitControllerConfig>> {
    void contextArg;
    return {
      kind: 'form',
      title: 'Connect HomeKit Device',
      description: 'Configure a HomeKit Accessory Protocol device from discovery, pairing data, or a cached accessory snapshot.',
      fields: [
        { name: 'host', label: 'Host', type: 'text' },
        { name: 'port', label: 'Port', type: 'number' },
        { name: 'setupCode', label: 'Setup code', type: 'password' },
        { name: 'allowInsecureSetupCode', label: 'Allow insecure setup code', type: 'boolean' },
        { name: 'pairingData', label: 'Pairing data JSON', type: 'text' },
        { name: 'snapshotJson', label: 'Accessory snapshot JSON', type: 'text' },
      ],
      submit: async (valuesArg) => this.finish(valuesArg, candidateArg),
    };
  }

  private async finish(valuesArg: Record<string, unknown>, candidateArg: IDiscoveryCandidate): Promise<IConfigFlowStep<IHomekitControllerConfig>> {
    const allowInsecureSetupCode = valuesArg.allowInsecureSetupCode === true || candidateArg.metadata?.allowInsecureSetupCode === true;
    const setupCode = this.formattedSetupCode(this.stringValue(valuesArg.setupCode) || this.stringValue(candidateArg.metadata?.setupCode), allowInsecureSetupCode);
    if (setupCode === false) {
      return { kind: 'error', title: 'Invalid setup code', error: 'HomeKit setup codes must use the 123-45-678 format and cannot be a known insecure code unless explicitly allowed.' };
    }

    const pairingData = this.jsonValue<IHomekitPairingData>(valuesArg.pairingData) || this.objectValue<IHomekitPairingData>(candidateArg.metadata?.pairingData);
    const snapshot = this.jsonValue<IHomekitControllerSnapshot>(valuesArg.snapshotJson) || this.objectValue<IHomekitControllerSnapshot>(candidateArg.metadata?.snapshot);
    const accessories = this.objectArrayValue<IHomekitAccessory>(candidateArg.metadata?.accessories) || snapshot?.accessories;
    const host = this.stringValue(valuesArg.host) || candidateArg.host || pairingData?.AccessoryIP || snapshot?.host;
    const port = this.numberValue(valuesArg.port) || candidateArg.port || pairingData?.AccessoryPort || snapshot?.port || (host ? 51826 : undefined);

    if (!setupCode && !pairingData && !snapshot && !accessories?.length) {
      return { kind: 'error', title: 'HomeKit data required', error: 'Provide a setup code, pairing data JSON, or a cached accessory snapshot.' };
    }

    const id = this.normalizeId(this.stringValue(candidateArg.id) || pairingData?.AccessoryPairingID || snapshot?.id);
    return {
      kind: 'done',
      title: 'HomeKit Device configured',
      config: {
        id,
        name: candidateArg.name || snapshot?.name,
        host,
        port,
        setupCode: setupCode || undefined,
        allowInsecureSetupCode: allowInsecureSetupCode || undefined,
        pairingData,
        paired: Boolean(pairingData || snapshot?.paired || candidateArg.metadata?.paired),
        transport: snapshot?.transport || (host ? 'ip' : 'snapshot'),
        model: candidateArg.model,
        manufacturer: candidateArg.manufacturer,
        category: candidateArg.metadata?.category as string | number | undefined,
        configNumber: this.numberValue(candidateArg.metadata?.configNumber) || snapshot?.configNumber,
        stateNumber: this.numberValue(candidateArg.metadata?.stateNumber) || snapshot?.stateNumber,
        accessories,
        snapshot,
        discovery: {
          source: candidateArg.source === 'manual' || candidateArg.source === 'mdns' || candidateArg.source === 'bluetooth' ? candidateArg.source : undefined,
          id,
          name: candidateArg.name,
          host,
          port,
          model: candidateArg.model,
          manufacturer: candidateArg.manufacturer,
          category: candidateArg.metadata?.category as string | number | undefined,
          paired: typeof candidateArg.metadata?.paired === 'boolean' ? candidateArg.metadata.paired : undefined,
          configNumber: this.numberValue(candidateArg.metadata?.configNumber),
          stateNumber: this.numberValue(candidateArg.metadata?.stateNumber),
          statusFlags: this.numberValue(candidateArg.metadata?.statusFlags),
          setupHash: this.stringValue(candidateArg.metadata?.setupHash),
          protocolVersion: this.stringValue(candidateArg.metadata?.protocolVersion),
          txt: this.objectValue<Record<string, string | undefined>>(candidateArg.metadata?.txt),
        },
        connected: snapshot?.connected,
      },
    };
  }

  private formattedSetupCode(valueArg: string | undefined, allowInsecureArg: boolean): string | false | undefined {
    if (!valueArg) {
      return undefined;
    }
    const match = pinFormat.exec(valueArg.trim());
    if (!match) {
      return false;
    }
    const compact = `${match[1]}${match[2]}${match[3]}`;
    if (!allowInsecureArg && insecureCodes.has(compact)) {
      return false;
    }
    return `${match[1]}-${match[2]}-${match[3]}`;
  }

  private jsonValue<TValue>(valueArg: unknown): TValue | undefined {
    if (typeof valueArg !== 'string' || !valueArg.trim()) {
      return undefined;
    }
    try {
      const parsed = JSON.parse(valueArg) as unknown;
      return this.isRecord(parsed) || Array.isArray(parsed) ? parsed as TValue : undefined;
    } catch {
      return undefined;
    }
  }

  private objectValue<TValue>(valueArg: unknown): TValue | undefined {
    return this.isRecord(valueArg) ? valueArg as TValue : undefined;
  }

  private objectArrayValue<TValue>(valueArg: unknown): TValue[] | undefined {
    return Array.isArray(valueArg) ? valueArg as TValue[] : undefined;
  }

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

  private numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg === 'string' && valueArg.trim()) {
      const value = Number.parseInt(valueArg, 10);
      return Number.isFinite(value) ? value : undefined;
    }
    return undefined;
  }

  private isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
    return typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
  }

  private normalizeId(valueArg?: string): string | undefined {
    return valueArg?.trim().toLowerCase();
  }
}
