import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import type { IFritzboxCallmonitorConfig, IFritzboxCallmonitorContactInput, IFritzboxCallmonitorSnapshot } from './fritzbox_callmonitor.types.js';
import { fritzboxCallmonitorDefaultHost, fritzboxCallmonitorDefaultName, fritzboxCallmonitorDefaultPort } from './fritzbox_callmonitor.types.js';

export class FritzboxCallmonitorConfigFlow implements IConfigFlow<IFritzboxCallmonitorConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IFritzboxCallmonitorConfig>> {
    void contextArg;
    return {
      kind: 'form',
      title: 'Connect FRITZ!Box Call Monitor',
      description: 'Configure the local FRITZ!Box call monitor TCP endpoint. Manual snapshots, call monitor lines, contacts, and prefixes are supported; live events require an enabled FRITZ!Box call monitor and a host or injected event source.',
      fields: [
        { name: 'host', label: `Host (${candidateArg.host || fritzboxCallmonitorDefaultHost})`, type: 'text', required: true },
        { name: 'port', label: `Call monitor TCP port (${candidateArg.port || fritzboxCallmonitorDefaultPort})`, type: 'number' },
        { name: 'username', label: 'Username (stored for parity; native phonebook login is not implemented)', type: 'text' },
        { name: 'password', label: 'Password (stored for parity; native phonebook login is not implemented)', type: 'password' },
        { name: 'phonebookName', label: 'Phonebook name', type: 'text' },
        { name: 'phonebookId', label: 'Phonebook id', type: 'number' },
        { name: 'serialNumber', label: 'FRITZ!Box serial number', type: 'text' },
        { name: 'prefixes', label: 'Prefixes (comma-separated list)', type: 'text' },
        { name: 'contactsJson', label: 'Contacts JSON array', type: 'text' },
        { name: 'eventLines', label: 'Call monitor event lines', type: 'text' },
        { name: 'snapshotJson', label: 'Snapshot JSON', type: 'text' },
      ],
      submit: async (valuesArg) => this.submit(candidateArg, valuesArg),
    };
  }

  private async submit(candidateArg: IDiscoveryCandidate, valuesArg: Record<string, unknown>): Promise<IConfigFlowStep<IFritzboxCallmonitorConfig>> {
    const snapshot = this.snapshotFromInput(valuesArg.snapshotJson || candidateArg.metadata?.snapshot);
    if (snapshot instanceof Error) {
      return { kind: 'error', title: 'Invalid FRITZ!Box Call Monitor snapshot', error: snapshot.message };
    }

    const contacts = this.contactsFromInput(valuesArg.contactsJson || candidateArg.metadata?.contacts);
    if (contacts instanceof Error) {
      return { kind: 'error', title: 'Invalid FRITZ!Box contacts', error: contacts.message };
    }

    const prefixes = this.prefixesFromInput(valuesArg.prefixes ?? candidateArg.metadata?.prefixes);
    if (prefixes instanceof Error) {
      return { kind: 'error', title: 'Invalid FRITZ!Box prefixes', error: prefixes.message };
    }

    const eventLines = this.eventLinesFromInput(valuesArg.eventLines || candidateArg.metadata?.eventLines || candidateArg.metadata?.events);
    if (eventLines instanceof Error) {
      return { kind: 'error', title: 'Invalid FRITZ!Box call monitor events', error: eventLines.message };
    }

    const host = this.stringValue(valuesArg.host) || candidateArg.host || snapshot?.monitor.host || fritzboxCallmonitorDefaultHost;
    const port = this.numberValue(valuesArg.port) || candidateArg.port || snapshot?.monitor.port || fritzboxCallmonitorDefaultPort;
    const phonebookId = this.numberValue(valuesArg.phonebookId) ?? snapshot?.monitor.phonebookId;
    const phonebookName = this.stringValue(valuesArg.phonebookName) || snapshot?.monitor.phonebookName || snapshot?.monitor.name || fritzboxCallmonitorDefaultName;
    const serialNumber = this.stringValue(valuesArg.serialNumber) || candidateArg.serialNumber || snapshot?.monitor.serialNumber;

    const config: IFritzboxCallmonitorConfig = {
      host,
      port,
      username: this.stringValue(valuesArg.username) || snapshot?.monitor.username,
      password: this.stringValue(valuesArg.password),
      phonebookId,
      phonebookName,
      serialNumber,
      uniqueId: candidateArg.id || serialNumber || snapshot?.monitor.id || `${host}:${port}`,
      name: candidateArg.name || phonebookName,
      model: candidateArg.model || snapshot?.monitor.model,
      prefixes,
      contacts,
      eventLines,
      snapshot,
      metadata: {
        discoverySource: candidateArg.source,
        discoveryMetadata: candidateArg.metadata,
        upstreamRequiresPasswordForPhonebook: true,
        nativeTcpCallMonitor: true,
        phonebookApiImplemented: false,
        liveEventsRequireHostOrClient: true,
      },
    };

    return {
      kind: 'done',
      title: 'FRITZ!Box Call Monitor configured',
      config,
    };
  }

  private snapshotFromInput(valueArg: unknown): IFritzboxCallmonitorSnapshot | undefined | Error {
    if (valueArg && typeof valueArg === 'object') {
      return valueArg as IFritzboxCallmonitorSnapshot;
    }
    const text = this.stringValue(valueArg);
    if (!text) {
      return undefined;
    }
    try {
      const parsed = JSON.parse(text) as IFritzboxCallmonitorSnapshot;
      if (!parsed || typeof parsed !== 'object' || !parsed.monitor || !parsed.call) {
        return new Error('Snapshot JSON must include monitor and call objects.');
      }
      return parsed;
    } catch (errorArg) {
      return errorArg instanceof Error ? errorArg : new Error(String(errorArg));
    }
  }

  private contactsFromInput(valueArg: unknown): IFritzboxCallmonitorContactInput[] | undefined | Error {
    if (Array.isArray(valueArg)) {
      return valueArg as IFritzboxCallmonitorContactInput[];
    }
    const text = this.stringValue(valueArg);
    if (!text) {
      return undefined;
    }
    try {
      const parsed = JSON.parse(text) as unknown;
      if (!Array.isArray(parsed)) {
        return new Error('Contacts JSON must be an array.');
      }
      return parsed as IFritzboxCallmonitorContactInput[];
    } catch (errorArg) {
      return errorArg instanceof Error ? errorArg : new Error(String(errorArg));
    }
  }

  private prefixesFromInput(valueArg: unknown): string[] | undefined | Error {
    if (Array.isArray(valueArg)) {
      return valueArg.map((itemArg) => String(itemArg).trim()).filter(Boolean);
    }
    if (valueArg === undefined || valueArg === null) {
      return undefined;
    }
    const text = typeof valueArg === 'string' ? valueArg : String(valueArg);
    if (!text.trim()) {
      return new Error('Prefixes are malformed, please check their format.');
    }
    return text.split(',').map((prefixArg) => prefixArg.trim()).filter(Boolean);
  }

  private eventLinesFromInput(valueArg: unknown): string[] | undefined | Error {
    if (Array.isArray(valueArg)) {
      return valueArg.map((lineArg) => typeof lineArg === 'string' ? lineArg.trim() : '').filter(Boolean);
    }
    const text = this.stringValue(valueArg);
    if (!text) {
      return undefined;
    }
    return text.split(/\r?\n/).map((lineArg) => lineArg.trim()).filter(Boolean);
  }

  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) && valueArg >= 0) {
      return Math.round(valueArg);
    }
    if (typeof valueArg === 'string' && valueArg.trim()) {
      const parsed = Number(valueArg);
      return Number.isFinite(parsed) && parsed >= 0 ? Math.round(parsed) : undefined;
    }
    return undefined;
  }
}
