import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import { FoscamApiAuthorizationError, FoscamApiConnectionError, FoscamClient } from './foscam.classes.client.js';
import { foscamDefaultConfigFromCandidate, foscamRtspPortFromMetadata, foscamStreamFromMetadata } from './foscam.discovery.js';
import type { IFoscamConfig, TFoscamProtocol, TFoscamStream } from './foscam.types.js';
import { foscamDefaultPort, foscamDefaultRtspPort, foscamDefaultTimeoutMs } from './foscam.types.js';

export class FoscamConfigFlow implements IConfigFlow<IFoscamConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IFoscamConfig>> {
    void contextArg;
    return {
      kind: 'form',
      title: 'Connect Foscam camera',
      description: 'Configure the local Foscam HTTP CGI endpoint. Use the camera HTTP port and optional RTSP port for stream source URLs.',
      fields: [
        { name: 'url', label: 'Base URL', type: 'text' },
        { name: 'host', label: 'Host', type: 'text' },
        { name: 'port', label: 'HTTP port', type: 'number' },
        { name: 'username', label: 'Username', type: 'text', required: true },
        { name: 'password', label: 'Password', type: 'password', required: true },
        { name: 'stream', label: 'Stream', type: 'select', options: [{ label: 'Main', value: 'Main' }, { label: 'Sub', value: 'Sub' }] },
        { name: 'rtspPort', label: 'RTSP port', type: 'number' },
        { name: 'name', label: 'Name', type: 'text' },
        { name: 'validateConnection', label: 'Validate connection now', type: 'boolean' },
      ],
      submit: async (valuesArg) => {
        const defaults = foscamDefaultConfigFromCandidate(candidateArg);
        const urlValue = this.stringValue(valuesArg.url) || this.stringMetadata(candidateArg, 'url');
        const endpoint = this.endpoint(urlValue, this.stringValue(valuesArg.host) || candidateArg.host || defaults.host, this.numberValue(valuesArg.port) || candidateArg.port || defaults.port, this.protocolMetadata(candidateArg) || defaults.protocol);
        if (!endpoint.host) {
          return { kind: 'error', error: 'Foscam requires a base URL or host.' };
        }
        const username = this.stringValue(valuesArg.username) || this.stringMetadata(candidateArg, 'username');
        const password = this.stringValue(valuesArg.password) || this.stringMetadata(candidateArg, 'password');
        if (!username || password === undefined) {
          return { kind: 'error', error: 'Foscam requires username and password.' };
        }
        const config: IFoscamConfig = {
          protocol: endpoint.protocol,
          host: endpoint.host,
          port: endpoint.port,
          url: endpoint.url,
          username,
          password,
          stream: this.streamValue(valuesArg.stream) || foscamStreamFromMetadata(candidateArg) || 'Main',
          rtspPort: this.numberValue(valuesArg.rtspPort) || foscamRtspPortFromMetadata(candidateArg) || foscamDefaultRtspPort,
          name: this.stringValue(valuesArg.name) || candidateArg.name || endpoint.host,
          uniqueId: candidateArg.id || candidateArg.macAddress || candidateArg.serialNumber || endpoint.host,
          manufacturer: candidateArg.manufacturer || 'Foscam',
          model: candidateArg.model,
          timeoutMs: foscamDefaultTimeoutMs,
        };

        if (this.booleanValue(valuesArg.validateConnection)) {
          try {
            const snapshot = await new FoscamClient(config).validateConnection();
            config.name = config.name || snapshot.deviceInfo.name;
            config.model = config.model || snapshot.deviceInfo.model;
          } catch (errorArg) {
            if (errorArg instanceof FoscamApiAuthorizationError) {
              return { kind: 'error', error: 'Invalid Foscam username or password.' };
            }
            if (errorArg instanceof FoscamApiConnectionError) {
              return { kind: 'error', error: 'Could not connect to the Foscam camera.' };
            }
            return { kind: 'error', error: errorArg instanceof Error ? errorArg.message : String(errorArg) };
          }
        }

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

  private endpoint(urlArg: string | undefined, hostArg: string | undefined, portArg: number | undefined, protocolArg: TFoscamProtocol | undefined): { protocol: TFoscamProtocol; host?: string; port: number; url?: string } {
    const url = safeUrl(urlArg || hostArg);
    if (url) {
      const protocol = url.protocol === 'https:' ? 'https' : 'http';
      const port = url.port ? Number(url.port) : protocol === 'https' ? 443 : foscamDefaultPort;
      return { protocol, host: url.hostname, port, url: `${protocol}://${url.hostname}:${port}` };
    }
    const protocol = protocolArg || 'http';
    const port = portArg || foscamDefaultPort;
    return { protocol, host: hostArg, port, url: hostArg ? `${protocol}://${hostArg}:${port}` : 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(valueArg);
      return Number.isFinite(value) ? value : undefined;
    }
    return undefined;
  }

  private booleanValue(valueArg: unknown): boolean {
    return valueArg === true || valueArg === 'true' || valueArg === '1' || valueArg === 'on';
  }

  private stringMetadata(candidateArg: IDiscoveryCandidate, keyArg: string): string | undefined {
    const value = candidateArg.metadata?.[keyArg];
    return typeof value === 'string' && value.trim() ? value.trim() : undefined;
  }

  private protocolMetadata(candidateArg: IDiscoveryCandidate): TFoscamProtocol | undefined {
    const protocol = candidateArg.metadata?.protocol;
    return protocol === 'http' || protocol === 'https' ? protocol : undefined;
  }

  private streamValue(valueArg: unknown): TFoscamStream | undefined {
    return valueArg === 'Main' || valueArg === 'Sub' ? valueArg : undefined;
  }
}

const safeUrl = (valueArg: string | undefined): URL | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    return new URL(valueArg);
  } catch {
    return undefined;
  }
};
