import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IDelugeHttpDiscoveryInput, IDelugeManualEntry, TDelugeProtocol } from './deluge.types.js';
import { delugeDefaultRpcPort, delugeDefaultWebPort, delugeDisplayName, delugeDomain, delugeManufacturer } from './deluge.types.js';

export class DelugeManualMatcher implements IDiscoveryMatcher<IDelugeManualEntry> {
  public id = 'deluge-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Deluge Web JSON-RPC endpoints, snapshots, raw data, or injected clients.';

  public async matches(inputArg: IDelugeManualEntry, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const endpoint = endpointFromManual(inputArg);
    const hasStaticSource = Boolean(inputArg.snapshot || inputArg.rawData || inputArg.client || inputArg.commandExecutor || inputArg.metadata?.snapshot || inputArg.metadata?.rawData || inputArg.metadata?.client);
    const hasHint = Boolean(endpoint.host || hasStaticSource || inputArg.metadata?.deluge || inputArg.metadata?.deluge_web || inputArg.metadata?.domain === delugeDomain);
    if (!hasHint) {
      return { matched: false, confidence: 'low', reason: 'Manual Deluge entry requires host, url, snapshot/raw data, injected client, or Deluge metadata.' };
    }
    const normalizedDeviceId = inputArg.id || endpoint.host || endpoint.url || inputArg.snapshot?.device.id || 'manual-deluge';
    return {
      matched: true,
      confidence: endpoint.host || hasStaticSource ? 'high' : 'medium',
      reason: hasStaticSource ? 'Manual entry contains Deluge snapshot/raw data or injected client metadata.' : 'Manual entry contains a Deluge Web endpoint.',
      normalizedDeviceId,
      candidate: {
        source: 'manual',
        integrationDomain: delugeDomain,
        id: normalizedDeviceId,
        host: endpoint.host,
        port: endpoint.webPort,
        name: inputArg.name || endpoint.host || delugeDisplayName,
        manufacturer: inputArg.manufacturer || delugeManufacturer,
        model: inputArg.model,
        metadata: {
          ...inputArg.metadata,
          protocol: endpoint.protocol,
          url: endpoint.url,
          webPort: endpoint.webPort,
          rpcPort: endpoint.rpcPort,
          password: inputArg.password,
          webPassword: inputArg.webPassword,
          username: inputArg.username || inputArg.daemonUsername,
          daemonHost: inputArg.daemonHost || endpoint.host,
          daemonPassword: inputArg.daemonPassword,
          snapshot: inputArg.snapshot || inputArg.metadata?.snapshot,
          rawData: inputArg.rawData || inputArg.metadata?.rawData,
          client: inputArg.client || inputArg.metadata?.client,
          commandExecutor: inputArg.commandExecutor || inputArg.metadata?.commandExecutor,
          discoveryProtocol: 'manual',
        },
      },
      metadata: {
        protocol: endpoint.protocol,
        url: endpoint.url,
        manualSupported: true,
      },
    };
  }
}

export class DelugeHttpMatcher implements IDiscoveryMatcher<IDelugeHttpDiscoveryInput> {
  public id = 'deluge-http-match';
  public source = 'http' as const;
  public description = 'Recognize HTTP candidates that point at a Deluge Web JSON-RPC endpoint.';

  public async matches(inputArg: IDelugeHttpDiscoveryInput, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const endpoint = endpointFromHttp(inputArg);
    const hasJsonPath = inputArg.path === '/json' || endpoint.url?.endsWith('/json');
    const hasDelugeHint = Boolean(inputArg.metadata?.deluge || inputArg.metadata?.deluge_web || inputArg.metadata?.server === 'deluge' || inputArg.name?.toLowerCase().includes('deluge'));
    const isDefaultWebPort = endpoint.webPort === delugeDefaultWebPort;
    if (!endpoint.host || (!hasJsonPath && !hasDelugeHint && !isDefaultWebPort)) {
      return { matched: false, confidence: 'low', reason: 'HTTP candidate does not look like a Deluge Web JSON-RPC endpoint.' };
    }
    const normalizedDeviceId = inputArg.id || endpoint.host;
    return {
      matched: true,
      confidence: hasJsonPath || hasDelugeHint ? 'high' : 'medium',
      reason: hasJsonPath ? 'HTTP candidate targets /json.' : isDefaultWebPort ? `HTTP candidate uses Deluge Web default port ${delugeDefaultWebPort}.` : 'HTTP candidate includes Deluge metadata.',
      normalizedDeviceId,
      candidate: {
        source: 'http',
        integrationDomain: delugeDomain,
        id: normalizedDeviceId,
        host: endpoint.host,
        port: endpoint.webPort,
        name: inputArg.name || endpoint.host,
        manufacturer: inputArg.manufacturer || delugeManufacturer,
        model: inputArg.model,
        metadata: {
          ...inputArg.metadata,
          protocol: endpoint.protocol,
          url: endpoint.url,
          webPort: endpoint.webPort,
          rpcPort: delugeDefaultRpcPort,
          discoveryProtocol: 'http',
        },
      },
      metadata: {
        protocol: endpoint.protocol,
        url: endpoint.url,
      },
    };
  }
}

export class DelugeCandidateValidator implements IDiscoveryValidator {
  public id = 'deluge-candidate-validator';
  public description = 'Validate that a Deluge candidate has a usable local source.';

  public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    if (candidateArg.integrationDomain && candidateArg.integrationDomain !== delugeDomain) {
      return { matched: false, confidence: 'low', reason: `Candidate belongs to ${candidateArg.integrationDomain}, not Deluge.` };
    }
    const endpoint = endpointFromCandidate(candidateArg);
    const hasStaticSource = Boolean(candidateArg.metadata?.snapshot || candidateArg.metadata?.rawData || candidateArg.metadata?.client);
    if (!endpoint.host && !hasStaticSource) {
      return { matched: false, confidence: 'low', reason: 'Deluge candidate lacks a host, snapshot, raw data, or injected client.' };
    }
    if (endpoint.host && (!Number.isInteger(endpoint.webPort) || endpoint.webPort < 1 || endpoint.webPort > 65535)) {
      return { matched: false, confidence: 'low', reason: 'Deluge candidate has an invalid web port.' };
    }
    return {
      matched: true,
      confidence: candidateArg.source === 'manual' || hasStaticSource ? 'high' : 'medium',
      reason: 'Candidate has enough Deluge metadata to start configuration.',
      normalizedDeviceId: candidateArg.id || endpoint.host || 'manual-deluge',
      candidate: {
        ...candidateArg,
        integrationDomain: delugeDomain,
        host: endpoint.host,
        port: endpoint.webPort,
        manufacturer: candidateArg.manufacturer || delugeManufacturer,
        metadata: {
          ...candidateArg.metadata,
          protocol: endpoint.protocol,
          url: endpoint.url,
          webPort: endpoint.webPort,
          rpcPort: endpoint.rpcPort,
        },
      },
      metadata: {
        protocol: endpoint.protocol,
        url: endpoint.url,
        manualSupported: candidateArg.source === 'manual',
      },
    };
  }
}

export const createDelugeDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: delugeDomain, displayName: delugeDisplayName })
    .addMatcher(new DelugeManualMatcher())
    .addMatcher(new DelugeHttpMatcher())
    .addValidator(new DelugeCandidateValidator());
};

const endpointFromManual = (inputArg: IDelugeManualEntry): { protocol: TDelugeProtocol; host?: string; webPort: number; rpcPort: number; url?: string } => {
  const url = safeUrl(inputArg.url || inputArg.host);
  if (url) {
    const protocol = url.protocol === 'https:' ? 'https' : 'http';
    const webPort = url.port ? Number(url.port) : protocol === 'https' ? 443 : delugeDefaultWebPort;
    return { protocol, host: url.hostname, webPort, rpcPort: inputArg.rpcPort || inputArg.daemonPort || inputArg.port || delugeDefaultRpcPort, url: url.toString().replace(/\/json$/, '').replace(/\/$/, '') };
  }
  const protocol = inputArg.protocol || 'http';
  const webPort = inputArg.webPort || inputArg.web_port || ((inputArg.rpcPort || inputArg.daemonPort) ? undefined : inputArg.port) || delugeDefaultWebPort;
  return { protocol, host: inputArg.host, webPort, rpcPort: inputArg.rpcPort || inputArg.daemonPort || ((inputArg.webPort || inputArg.web_port) ? inputArg.port : undefined) || delugeDefaultRpcPort, url: inputArg.host ? `${protocol}://${inputArg.host}:${webPort}` : undefined };
};

const endpointFromHttp = (inputArg: IDelugeHttpDiscoveryInput): { protocol: TDelugeProtocol; host?: string; webPort: number; url?: string } => {
  const metadataUrl = typeof inputArg.metadata?.url === 'string' ? inputArg.metadata.url : undefined;
  const url = safeUrl(inputArg.url || metadataUrl || inputArg.host);
  if (url) {
    const protocol = url.protocol === 'https:' ? 'https' : 'http';
    const webPort = url.port ? Number(url.port) : protocol === 'https' ? 443 : (inputArg.port || delugeDefaultWebPort);
    return { protocol, host: url.hostname, webPort, url: `${protocol}://${url.hostname}:${webPort}` };
  }
  const protocol = inputArg.metadata?.protocol === 'https' ? 'https' : 'http';
  const webPort = inputArg.port || delugeDefaultWebPort;
  return { protocol, host: inputArg.host, webPort, url: inputArg.host ? `${protocol}://${inputArg.host}:${webPort}` : undefined };
};

const endpointFromCandidate = (candidateArg: IDiscoveryCandidate): { protocol: TDelugeProtocol; host?: string; webPort: number; rpcPort: number; url?: string } => {
  const protocol = candidateArg.metadata?.protocol === 'https' ? 'https' : 'http';
  const metadataUrl = typeof candidateArg.metadata?.url === 'string' ? candidateArg.metadata.url : undefined;
  const url = safeUrl(metadataUrl || candidateArg.host);
  if (url) {
    const urlProtocol = url.protocol === 'https:' ? 'https' : 'http';
    const webPort = url.port ? Number(url.port) : urlProtocol === 'https' ? 443 : delugeDefaultWebPort;
    return { protocol: urlProtocol, host: url.hostname, webPort, rpcPort: numberValue(candidateArg.metadata?.rpcPort) || delugeDefaultRpcPort, url: `${urlProtocol}://${url.hostname}:${webPort}` };
  }
  const webPort = numberValue(candidateArg.metadata?.webPort ?? candidateArg.metadata?.web_port) || candidateArg.port || delugeDefaultWebPort;
  return { protocol, host: candidateArg.host, webPort, rpcPort: numberValue(candidateArg.metadata?.rpcPort) || delugeDefaultRpcPort, url: candidateArg.host ? `${protocol}://${candidateArg.host}:${webPort}` : metadataUrl };
};

const numberValue = (valueArg: unknown): number | undefined => {
  if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
    return valueArg;
  }
  if (typeof valueArg === 'string' && valueArg.trim()) {
    const parsed = Number(valueArg);
    return Number.isFinite(parsed) ? parsed : undefined;
  }
  return undefined;
};

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