import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type {
  IDiscoveryCandidate,
  IDiscoveryContext,
  IDiscoveryMatch,
  IDiscoveryMatcher,
  IDiscoveryValidator,
} from '../../core/types.js';
import type { IOnvifManualDiscoveryRecord, IOnvifMdnsRecord, IOnvifWsDiscoveryRecord } from './onvif.types.js';
import { onvifDefaultPort } from './onvif.types.js';

export class OnvifWsDiscoveryMatcher implements IDiscoveryMatcher<IOnvifWsDiscoveryRecord> {
  public id = 'onvif-ws-discovery-match';
  public source = 'custom' as const;
  public description = 'Recognize ONVIF WS-Discovery ProbeMatch records for NetworkVideoTransmitter devices.';

  public async matches(recordArg: IOnvifWsDiscoveryRecord): Promise<IDiscoveryMatch> {
    const xaddrs = this.list(recordArg.xaddrs ?? recordArg.xAddrs ?? recordArg.XAddrs);
    const scopes = this.scopeValues(recordArg.scopes);
    const types = this.list(recordArg.types);
    const epr = recordArg.epr || recordArg.endpointReference || recordArg.endpoint_reference;
    const firstUrl = this.firstUrl(xaddrs);
    const scopeInfo = this.infoFromScopes(scopes);
    const hasOnvifType = types.some((typeArg) => /NetworkVideoTransmitter|onvif/i.test(typeArg));
    const hasOnvifScope = scopes.some((scopeArg) => /onvif:\/\/www\.onvif\.org\/Profile\/Streaming/i.test(scopeArg));
    const hasOnvifXaddr = xaddrs.some((xaddrArg) => /\/onvif\//i.test(xaddrArg));

    if (!hasOnvifType && !hasOnvifScope && !hasOnvifXaddr) {
      return { matched: false, confidence: 'low', reason: 'WS-Discovery record does not contain ONVIF type, streaming scope, or ONVIF XAddr.' };
    }

    const normalizedDeviceId = normalizeMac(scopeInfo.macAddress) || this.normalizedEpr(epr) || firstUrl?.host;
    return {
      matched: true,
      confidence: hasOnvifScope || hasOnvifType ? 'certain' : 'high',
      reason: 'WS-Discovery record advertises an ONVIF camera service.',
      normalizedDeviceId,
      candidate: {
        source: 'custom',
        integrationDomain: 'onvif',
        id: normalizedDeviceId,
        host: firstUrl?.hostname,
        port: firstUrl?.port,
        name: scopeInfo.name || recordArg.name || epr,
        manufacturer: scopeInfo.manufacturer,
        model: scopeInfo.hardware,
        macAddress: normalizeMac(scopeInfo.macAddress),
        metadata: {
          discoveryProtocol: 'ws-discovery',
          endpointReference: epr,
          xaddrs,
          scopes,
          serviceTypes: types,
          transport: firstUrl?.protocol,
          raw: recordArg.metadata,
        },
      },
      metadata: {
        discoveryProtocol: 'ws-discovery',
        xaddrs,
        scopes,
      },
    };
  }

  private list(valueArg: string[] | string | undefined): string[] {
    if (!valueArg) {
      return [];
    }
    if (Array.isArray(valueArg)) {
      return valueArg.map((value) => String(value)).filter(Boolean);
    }
    return String(valueArg).split(/\s+/).filter(Boolean);
  }

  private scopeValues(valueArg: IOnvifWsDiscoveryRecord['scopes']): string[] {
    if (!valueArg) {
      return [];
    }
    if (typeof valueArg === 'string') {
      return this.list(valueArg);
    }
    return valueArg.map((scopeArg) => {
      if (typeof scopeArg === 'string') {
        return scopeArg;
      }
      return scopeArg.value || scopeArg.Value || '';
    }).filter(Boolean);
  }

  private firstUrl(xaddrsArg: string[]): { hostname: string; host: string; port: number; protocol: string } | undefined {
    for (const xaddr of xaddrsArg) {
      try {
        const url = new URL(xaddr);
        return {
          hostname: url.hostname,
          host: url.host,
          port: url.port ? Number(url.port) : url.protocol === 'https:' ? 443 : onvifDefaultPort,
          protocol: url.protocol.replace(':', ''),
        };
      } catch {
        continue;
      }
    }
    return undefined;
  }

  private infoFromScopes(scopesArg: string[]): { name?: string; hardware?: string; macAddress?: string; manufacturer?: string } {
    const info: { name?: string; hardware?: string; macAddress?: string; manufacturer?: string } = {};
    for (const scope of scopesArg) {
      const lower = scope.toLowerCase();
      const value = decodeURIComponent(scope.split('/').pop() || '');
      if (lower.startsWith('onvif://www.onvif.org/name')) {
        info.name = value;
      } else if (lower.startsWith('onvif://www.onvif.org/hardware')) {
        info.hardware = value;
      } else if (lower.startsWith('onvif://www.onvif.org/mac')) {
        info.macAddress = value;
      } else if (lower.startsWith('onvif://www.onvif.org/manufacturer')) {
        info.manufacturer = value;
      }
    }
    return info;
  }

  private normalizedEpr(valueArg?: string): string | undefined {
    if (!valueArg) {
      return undefined;
    }
    return valueArg.replace(/^urn:uuid:/i, '').trim() || undefined;
  }
}

export class OnvifMdnsMatcher implements IDiscoveryMatcher<IOnvifMdnsRecord> {
  public id = 'onvif-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize ONVIF-capable camera mDNS records such as _onvif._tcp.local.';

  public async matches(recordArg: IOnvifMdnsRecord): Promise<IDiscoveryMatch> {
    const txt = { ...(recordArg.properties || {}), ...(recordArg.txt || {}) };
    const type = this.stringValue(recordArg.type).toLowerCase();
    const name = this.stringValue(recordArg.name);
    const host = this.stringValue(recordArg.host || recordArg.hostname || recordArg.addresses?.[0]);
    const txtValues = Object.values(txt).map((valueArg) => String(valueArg).toLowerCase());
    const txtKeys = Object.keys(txt).map((keyArg) => keyArg.toLowerCase());
    const hasOnvifHint = type.includes('_onvif')
      || name.toLowerCase().includes('onvif')
      || txtKeys.some((keyArg) => keyArg.includes('onvif'))
      || txtValues.some((valueArg) => valueArg.includes('onvif'));

    if (!hasOnvifHint) {
      return { matched: false, confidence: 'low', reason: 'mDNS record does not advertise ONVIF metadata.' };
    }

    const macAddress = normalizeMac(this.stringValue(txt.mac || txt.macAddress || txt.mac_address || txt.hwaddr));
    const normalizedDeviceId = macAddress || this.stringValue(txt.serial || txt.serialNumber || txt.serial_number) || host;
    return {
      matched: true,
      confidence: type.includes('_onvif') ? 'high' : 'medium',
      reason: 'mDNS record contains ONVIF camera metadata.',
      normalizedDeviceId,
      candidate: {
        source: 'mdns',
        integrationDomain: 'onvif',
        id: normalizedDeviceId,
        host,
        port: recordArg.port || onvifDefaultPort,
        name,
        manufacturer: this.stringValue(txt.manufacturer || txt.vendor),
        model: this.stringValue(txt.model || txt.hardware),
        serialNumber: this.stringValue(txt.serial || txt.serialNumber || txt.serial_number),
        macAddress,
        metadata: {
          discoveryProtocol: 'mdns',
          type: recordArg.type,
          txt,
        },
      },
    };
  }

  private stringValue(valueArg: unknown): string {
    return typeof valueArg === 'string' ? valueArg.trim() : valueArg === undefined || valueArg === null ? '' : String(valueArg).trim();
  }
}

export class OnvifManualMatcher implements IDiscoveryMatcher<IOnvifManualDiscoveryRecord> {
  public id = 'onvif-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manually configured ONVIF camera entries and cached snapshots.';

  public async matches(recordArg: IOnvifManualDiscoveryRecord): Promise<IDiscoveryMatch> {
    const snapshot = recordArg.snapshot || recordArg.discovery?.snapshot;
    const host = recordArg.host || snapshot?.host || snapshot?.cameras[0]?.host;
    const port = recordArg.port || snapshot?.port || snapshot?.cameras[0]?.port || onvifDefaultPort;
    if (!host && !snapshot) {
      return { matched: false, confidence: 'low', reason: 'Manual ONVIF entries require a host or cached snapshot.' };
    }
    const deviceInfo = recordArg.deviceInfo || snapshot?.deviceInfo || snapshot?.cameras[0]?.deviceInfo;
    const normalizedDeviceId = normalizeMac(deviceInfo?.macAddress) || deviceInfo?.serialNumber || recordArg.id || recordArg.discovery?.id || host;
    return {
      matched: true,
      confidence: snapshot ? 'certain' : 'medium',
      reason: snapshot ? 'Manual entry contains an ONVIF snapshot.' : 'Manual entry contains ONVIF host configuration.',
      normalizedDeviceId,
      candidate: {
        source: 'manual',
        integrationDomain: 'onvif',
        id: normalizedDeviceId,
        host,
        port,
        name: recordArg.name || snapshot?.name || deviceInfo?.name,
        manufacturer: deviceInfo?.manufacturer,
        model: deviceInfo?.model,
        serialNumber: deviceInfo?.serialNumber,
        macAddress: normalizeMac(deviceInfo?.macAddress),
        metadata: {
          discoveryProtocol: 'manual',
          snapshot,
          profiles: recordArg.profiles,
          streams: recordArg.streams,
          transport: recordArg.transport || snapshot?.transport || 'http',
          ...recordArg.metadata,
        },
      },
    };
  }
}

export class OnvifCandidateValidator implements IDiscoveryValidator {
  public id = 'onvif-candidate-validator';
  public description = 'Confirm an ONVIF discovery candidate has a usable host/port or cached snapshot.';

  public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    if (candidateArg.integrationDomain && candidateArg.integrationDomain !== 'onvif') {
      return { matched: false, confidence: 'low', reason: `Candidate belongs to ${candidateArg.integrationDomain}, not ONVIF.` };
    }
    const snapshot = candidateArg.metadata?.snapshot;
    const port = candidateArg.port || onvifDefaultPort;
    if (!Number.isInteger(port) || port < 1 || port > 65535) {
      return { matched: false, confidence: 'low', reason: 'ONVIF candidate has an invalid port.' };
    }
    if (!candidateArg.host && !snapshot) {
      return { matched: false, confidence: 'low', reason: 'ONVIF candidate requires host information or a cached snapshot.' };
    }
    const id = normalizeMac(candidateArg.macAddress) || candidateArg.serialNumber || candidateArg.id || candidateArg.host;
    return {
      matched: true,
      confidence: candidateArg.id || candidateArg.macAddress || snapshot ? 'high' : 'medium',
      reason: 'Candidate has enough ONVIF metadata to start configuration.',
      candidate: candidateArg,
      normalizedDeviceId: id,
      metadata: {
        discoveryProtocol: candidateArg.metadata?.discoveryProtocol || candidateArg.source,
        wsDiscoverySupported: candidateArg.metadata?.discoveryProtocol === 'ws-discovery',
        mdnsSupported: candidateArg.source === 'mdns',
        manualSupported: candidateArg.source === 'manual',
      },
    };
  }
}

export const createOnvifDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({
    integrationDomain: 'onvif',
    displayName: 'ONVIF',
  })
    .addMatcher(new OnvifWsDiscoveryMatcher())
    .addMatcher(new OnvifMdnsMatcher())
    .addMatcher(new OnvifManualMatcher())
    .addValidator(new OnvifCandidateValidator());
};

const normalizeMac = (valueArg?: string): string | undefined => {
  if (!valueArg) {
    return undefined;
  }
  const compact = valueArg.replace(/[^a-fA-F0-9]/g, '').toLowerCase();
  if (compact.length !== 12) {
    return undefined;
  }
  return compact.match(/.{1,2}/g)?.join(':');
};
