import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import { AgentDvrApiConnectionError, AgentDvrClient } from './agent_dvr.classes.client.js';
import { agentDvrDefaultConfigFromCandidate } from './agent_dvr.discovery.js';
import type { IAgentDvrConfig, IAgentDvrSnapshot, TAgentDvrProtocol } from './agent_dvr.types.js';
import { agentDvrDefaultPort, agentDvrDefaultTimeoutMs } from './agent_dvr.types.js';

export class AgentDvrConfigFlow implements IConfigFlow<IAgentDvrConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IAgentDvrConfig>> {
    void contextArg;
    return {
      kind: 'form',
      title: 'Connect Agent DVR',
      description: 'Configure the local Agent DVR HTTP API endpoint. The default Agent DVR port is 8090.',
      fields: [
        { name: 'url', label: 'Base URL', type: 'text' },
        { name: 'host', label: 'Host', type: 'text' },
        { name: 'port', label: 'HTTP port', type: 'number' },
        { name: 'name', label: 'Name', type: 'text' },
        { name: 'validateConnection', label: 'Validate connection now', type: 'boolean' },
        { name: 'snapshotJson', label: 'Offline snapshot JSON', type: 'text' },
      ],
      submit: async (valuesArg) => {
        const snapshot = this.snapshotValue(valuesArg.snapshotJson) || this.snapshotMetadata(candidateArg);
        const defaults = agentDvrDefaultConfigFromCandidate(candidateArg);
        const endpoint = this.endpoint(
          this.stringValue(valuesArg.url) || this.stringMetadata(candidateArg, 'serverUrl') || this.stringMetadata(candidateArg, 'url') || defaults.url,
          this.stringValue(valuesArg.host) || candidateArg.host || defaults.host,
          this.numberValue(valuesArg.port) || candidateArg.port || defaults.port,
          this.protocolMetadata(candidateArg) || defaults.protocol
        );

        if (!endpoint.host && !snapshot) {
          return { kind: 'error', error: 'Agent DVR requires a base URL, host, or offline snapshot JSON.' };
        }

        const config: IAgentDvrConfig = {
          protocol: endpoint.protocol,
          host: endpoint.host,
          port: endpoint.port,
          url: endpoint.url,
          serverUrl: endpoint.url,
          name: this.stringValue(valuesArg.name) || candidateArg.name || snapshot?.server.name || endpoint.host,
          uniqueId: candidateArg.id || candidateArg.serialNumber || snapshot?.server.unique || endpoint.host,
          timeoutMs: agentDvrDefaultTimeoutMs,
          snapshot,
          metadata: {
            discoverySource: candidateArg.source,
          },
        };

        if (this.booleanValue(valuesArg.validateConnection)) {
          try {
            const liveSnapshot = await new AgentDvrClient(config).validateConnection();
            config.name = liveSnapshot.server.name || config.name;
            config.uniqueId = liveSnapshot.server.unique || config.uniqueId;
          } catch (errorArg) {
            if (errorArg instanceof AgentDvrApiConnectionError) {
              return { kind: 'error', error: 'Could not connect to the Agent DVR server.' };
            }
            return { kind: 'error', error: errorArg instanceof Error ? errorArg.message : String(errorArg) };
          }
        }

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

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

  private snapshotMetadata(candidateArg: IDiscoveryCandidate): IAgentDvrSnapshot | undefined {
    const snapshot = candidateArg.metadata?.snapshot;
    return this.isSnapshot(snapshot) ? snapshot : undefined;
  }

  private snapshotValue(valueArg: unknown): IAgentDvrSnapshot | undefined {
    const value = this.stringValue(valueArg);
    if (!value) {
      return undefined;
    }
    try {
      const parsed = JSON.parse(value) as unknown;
      return this.isSnapshot(parsed) ? parsed : undefined;
    } catch {
      return undefined;
    }
  }

  private isSnapshot(valueArg: unknown): valueArg is IAgentDvrSnapshot {
    return Boolean(valueArg && typeof valueArg === 'object' && 'server' in valueArg && 'devices' in valueArg);
  }
}

const safeUrl = (valueArg: string | undefined): URL | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    const url = new URL(valueArg);
    if (url.protocol === 'http:' || url.protocol === 'https:') {
      return url;
    }
  } catch {
    // Try again below with an explicit local HTTP scheme.
  }
  try {
    return new URL(`http://${valueArg}`);
  } catch {
    return undefined;
  }
};
