import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { OpnsenseMapper } from './opnsense.mapper.js';
import type { IOpnsenseManualDiscoveryRecord, IOpnsenseSnapshot } from './opnsense.types.js';
import { opnsenseDefaultPort, opnsenseDefaultVerifySsl, opnsenseDomain } from './opnsense.types.js';

const opnsenseTextHints = ['opnsense', 'opn sense', 'deciso'];

export class OpnsenseManualMatcher implements IDiscoveryMatcher<IOpnsenseManualDiscoveryRecord> {
  public id = 'opnsense-manual-https-match';
  public source = 'manual' as const;
  public description = 'Recognize manual OPNsense HTTPS setup entries, including snapshot-only records.';

  public async matches(inputArg: IOpnsenseManualDiscoveryRecord): Promise<IDiscoveryMatch> {
    const metadata = inputArg.metadata || {};
    const snapshot = inputArg.snapshot || metadata.snapshot as IOpnsenseSnapshot | undefined;
    const endpoint = parseEndpoint(inputArg.url || inputArg.host || snapshot?.router.url || snapshot?.router.host);
    const mac = OpnsenseMapper.normalizeMac(inputArg.macAddress || snapshot?.router.macAddress);
    const text = [inputArg.integrationDomain, inputArg.manufacturer, inputArg.model, inputArg.name, metadata.manufacturer, metadata.model, metadata.name, snapshot?.router.manufacturer, snapshot?.router.model, snapshot?.router.name]
      .filter((valueArg): valueArg is string => typeof valueArg === 'string')
      .join(' ')
      .toLowerCase();
    const hasSnapshot = Boolean(snapshot);
    const matched = inputArg.integrationDomain === opnsenseDomain
      || metadata.opnsense === true
      || hasSnapshot
      || opnsenseTextHints.some((hintArg) => text.includes(hintArg))
      || Boolean(endpoint.host && !text);

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain OPNsense setup hints.' };
    }

    if (endpoint.error) {
      return { matched: false, confidence: 'medium', reason: endpoint.error };
    }

    const port = inputArg.port || endpoint.port || snapshot?.router.port || opnsenseDefaultPort;
    const id = inputArg.id || mac || snapshot?.router.id || (endpoint.host ? `${endpoint.host}:${port}` : undefined);
    return {
      matched: true,
      confidence: hasSnapshot || mac ? 'certain' : endpoint.host ? 'high' : 'medium',
      reason: hasSnapshot ? 'Manual entry includes an OPNsense snapshot.' : 'Manual entry can start OPNsense HTTPS setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: opnsenseDomain,
        id,
        host: endpoint.host,
        port,
        name: inputArg.name || snapshot?.router.name || endpoint.host || 'OPNsense',
        manufacturer: inputArg.manufacturer || snapshot?.router.manufacturer || 'OPNsense',
        model: inputArg.model || snapshot?.router.model || 'OPNsense Firewall',
        macAddress: mac,
        metadata: {
          ...metadata,
          opnsense: true,
          protocol: 'https',
          ssl: true,
          verifySsl: inputArg.verifySsl ?? opnsenseDefaultVerifySsl,
          url: endpoint.host ? endpointUrl(endpoint.host, port) : inputArg.url,
          hasSnapshot,
          liveHttpImplemented: false,
          trackerInterfaces: inputArg.trackerInterfaces,
          snapshot,
        },
      },
      metadata: { hasSnapshot, protocol: 'https', liveHttpImplemented: false },
    };
  }
}

export class OpnsenseHttpsCandidateValidator implements IDiscoveryValidator {
  public id = 'opnsense-https-candidate-validator';
  public description = 'Validate OPNsense candidates have HTTPS metadata and a host or snapshot before config flow.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const snapshot = metadata.snapshot as IOpnsenseSnapshot | undefined;
    const endpoint = parseEndpoint(metadata.url as string | undefined || candidateArg.host || snapshot?.router.url || snapshot?.router.host);
    const text = [candidateArg.integrationDomain, candidateArg.manufacturer, candidateArg.model, candidateArg.name, metadata.manufacturer, metadata.model, metadata.name]
      .filter((valueArg): valueArg is string => typeof valueArg === 'string')
      .join(' ')
      .toLowerCase();
    const matched = candidateArg.integrationDomain === opnsenseDomain
      || metadata.opnsense === true
      || Boolean(snapshot)
      || opnsenseTextHints.some((hintArg) => text.includes(hintArg));
    const hasUsableSource = Boolean(endpoint.host || snapshot);

    if (endpoint.error) {
      return { matched: false, confidence: matched ? 'medium' : 'low', reason: endpoint.error };
    }
    if (!matched || !hasUsableSource) {
      return {
        matched: false,
        confidence: matched ? 'medium' : 'low',
        reason: matched ? 'OPNsense candidate lacks a usable HTTPS host or snapshot.' : 'Candidate is not OPNsense.',
      };
    }

    const mac = OpnsenseMapper.normalizeMac(candidateArg.macAddress || snapshot?.router.macAddress);
    const port = candidateArg.port || endpoint.port || snapshot?.router.port || opnsenseDefaultPort;
    const normalizedDeviceId = candidateArg.id || mac || (endpoint.host ? `${endpoint.host}:${port}` : snapshot?.router.id);
    return {
      matched: true,
      confidence: mac || snapshot ? 'certain' : endpoint.host ? 'high' : 'medium',
      reason: 'Candidate has OPNsense metadata and a usable local HTTPS source.',
      normalizedDeviceId,
      candidate: {
        ...candidateArg,
        id: candidateArg.id || normalizedDeviceId,
        host: endpoint.host || candidateArg.host,
        port,
        macAddress: mac || candidateArg.macAddress,
        metadata: {
          ...metadata,
          protocol: 'https',
          ssl: true,
          verifySsl: metadata.verifySsl ?? opnsenseDefaultVerifySsl,
          liveHttpImplemented: false,
        },
      },
      metadata: { protocol: 'https', liveHttpImplemented: false },
    };
  }
}

export const createOpnsenseDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: opnsenseDomain, displayName: 'OPNsense' })
    .addMatcher(new OpnsenseManualMatcher())
    .addValidator(new OpnsenseHttpsCandidateValidator());
};

const parseEndpoint = (valueArg: string | undefined): { host?: string; port?: number; error?: string } => {
  if (!valueArg) return {};
  try {
    const parsed = new URL(valueArg.includes('://') ? valueArg : `https://${valueArg}`);
    if (parsed.protocol !== 'https:') {
      return { error: 'OPNsense discovery only accepts local HTTPS candidates.' };
    }
    return { host: parsed.hostname, port: parsed.port ? Number(parsed.port) : undefined };
  } catch {
    return { error: 'OPNsense endpoint must be a hostname, IP address, or HTTPS URL.' };
  }
};

const endpointUrl = (hostArg: string, portArg: number | undefined): string => {
  const port = portArg || opnsenseDefaultPort;
  return `https://${hostArg}${port === opnsenseDefaultPort ? '' : `:${port}`}`;
};
