import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { UnifiMapper } from './unifi.mapper.js';
import type { IUnifiDiscoveryDeviceRecord, IUnifiManualDiscoveryEntry, IUnifiMdnsRecord, IUnifiSsdpRecord } from './unifi.types.js';
import { unifiDefaultPort, unifiDefaultSite } from './unifi.types.js';

const unifiModels = ['unifi', 'dream machine', 'cloud key', 'udm', 'uck', 'ucg', 'uxg'];

export class UnifiMdnsMatcher implements IDiscoveryMatcher<IUnifiMdnsRecord> {
  public id = 'unifi-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize UniFi Network controller mDNS advertisements.';

  public async matches(recordArg: IUnifiMdnsRecord): Promise<IDiscoveryMatch> {
    const type = recordArg.type?.toLowerCase() || '';
    const name = recordArg.name?.toLowerCase() || '';
    const model = (recordArg.txt?.model || recordArg.txt?.modelid || '').toLowerCase();
    const matched = type === '_unifi._tcp.local.' || type === '_ubnt._tcp.local.' || name.includes('unifi') || unifiModels.some((modelArg) => model.includes(modelArg));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a UniFi controller advertisement.' };
    }
    const mac = UnifiMapper.normalizeMac(recordArg.txt?.mac || recordArg.txt?.hw_addr);
    const id = recordArg.txt?.controller_uuid || recordArg.txt?.uuid || mac || recordArg.name;
    return {
      matched: true,
      confidence: id ? 'certain' : 'high',
      reason: 'mDNS record contains UniFi controller metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: 'unifi',
        id,
        host: recordArg.host,
        port: recordArg.port || unifiDefaultPort,
        name: recordArg.txt?.name || recordArg.name,
        manufacturer: 'Ubiquiti Networks',
        model: recordArg.txt?.model || recordArg.txt?.modelid || 'UniFi Network',
        macAddress: mac,
        metadata: {
          mdnsName: recordArg.name,
          mdnsType: recordArg.type,
          txt: recordArg.txt,
        },
      },
    };
  }
}

export class UnifiSsdpMatcher implements IDiscoveryMatcher<IUnifiSsdpRecord> {
  public id = 'unifi-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize UniFi OS consoles from SSDP Ubiquiti metadata.';

  public async matches(recordArg: IUnifiSsdpRecord): Promise<IDiscoveryMatch> {
    const headers = this.lowerHeaders(recordArg.headers || {});
    const manufacturer = (recordArg.manufacturer || headers.manufacturer || '').toLowerCase();
    const modelDescription = (recordArg.modelDescription || headers.modeldescription || headers.model_description || '').toLowerCase();
    const modelName = (recordArg.modelName || headers.modelname || '').toLowerCase();
    const matched = manufacturer.includes('ubiquiti') && unifiModels.some((modelArg) => `${modelDescription} ${modelName}`.includes(modelArg));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'SSDP record is not a UniFi OS console.' };
    }
    return {
      matched: true,
      confidence: recordArg.usn || recordArg.udn ? 'certain' : 'high',
      reason: 'SSDP record matches UniFi OS console metadata.',
      normalizedDeviceId: recordArg.usn || recordArg.udn,
      candidate: {
        source: 'ssdp',
        integrationDomain: 'unifi',
        id: recordArg.usn || recordArg.udn,
        host: recordArg.host || this.hostFromLocation(recordArg.location),
        port: recordArg.port || this.portFromLocation(recordArg.location) || unifiDefaultPort,
        manufacturer: 'Ubiquiti Networks',
        model: recordArg.modelDescription || recordArg.modelName || 'UniFi Network',
        metadata: {
          location: recordArg.location,
          server: recordArg.server,
          st: recordArg.st,
          nt: recordArg.nt,
        },
      },
    };
  }

  private lowerHeaders(headersArg: Record<string, string | undefined>): Record<string, string | undefined> {
    return Object.fromEntries(Object.entries(headersArg).map(([keyArg, valueArg]) => [keyArg.toLowerCase(), valueArg]));
  }

  private hostFromLocation(locationArg?: string): string | undefined {
    if (!locationArg) return undefined;
    try {
      return new URL(locationArg).hostname;
    } catch {
      return undefined;
    }
  }

  private portFromLocation(locationArg?: string): number | undefined {
    if (!locationArg) return undefined;
    try {
      const url = new URL(locationArg);
      return url.port ? Number(url.port) : undefined;
    } catch {
      return undefined;
    }
  }
}

export class UnifiManualMatcher implements IDiscoveryMatcher<IUnifiManualDiscoveryEntry> {
  public id = 'unifi-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual UniFi Network controller setup entries.';

  public async matches(inputArg: IUnifiManualDiscoveryEntry): Promise<IDiscoveryMatch> {
    const manufacturer = inputArg.manufacturer?.toLowerCase() || '';
    const model = inputArg.model?.toLowerCase() || '';
    const name = inputArg.name?.toLowerCase() || '';
    const matched = Boolean(inputArg.host || manufacturer.includes('ubiquiti') || unifiModels.some((modelArg) => `${model} ${name}`.includes(modelArg)) || inputArg.services?.network || inputArg.metadata?.unifi);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain UniFi setup hints.' };
    }
    return {
      matched: true,
      confidence: inputArg.host ? 'high' : 'medium',
      reason: 'Manual entry can start UniFi Network setup.',
      normalizedDeviceId: inputArg.id || UnifiMapper.normalizeMac(inputArg.macAddress),
      candidate: {
        source: 'manual',
        integrationDomain: 'unifi',
        id: inputArg.id,
        host: inputArg.host,
        port: inputArg.port || unifiDefaultPort,
        name: inputArg.name,
        manufacturer: 'Ubiquiti Networks',
        model: inputArg.model || 'UniFi Network',
        macAddress: UnifiMapper.normalizeMac(inputArg.macAddress),
        metadata: {
          ...inputArg.metadata,
          site: inputArg.site || unifiDefaultSite,
        },
      },
    };
  }
}

export class UnifiDiscoveryDeviceMatcher implements IDiscoveryMatcher<IUnifiDiscoveryDeviceRecord> {
  public id = 'unifi-discovery-device-match';
  public source = 'custom' as const;
  public description = 'Recognize records returned by UniFi Discovery scans.';

  public async matches(recordArg: IUnifiDiscoveryDeviceRecord): Promise<IDiscoveryMatch> {
    if (!recordArg.services?.network && !recordArg.source_ip) {
      return { matched: false, confidence: 'low', reason: 'Discovery record does not expose the UniFi Network service.' };
    }
    const mac = UnifiMapper.normalizeMac(recordArg.hw_addr);
    return {
      matched: true,
      confidence: recordArg.services?.network ? 'certain' : 'medium',
      reason: 'UniFi Discovery record exposes the Network service.',
      normalizedDeviceId: mac,
      candidate: {
        source: 'custom',
        integrationDomain: 'unifi',
        id: mac,
        host: recordArg.direct_connect_domain || recordArg.source_ip,
        port: unifiDefaultPort,
        name: recordArg.name,
        manufacturer: 'Ubiquiti Networks',
        model: recordArg.model || 'UniFi Network',
        macAddress: mac,
        metadata: recordArg as Record<string, unknown>,
      },
    };
  }
}

export class UnifiCandidateValidator implements IDiscoveryValidator {
  public id = 'unifi-candidate-validator';
  public description = 'Validate UniFi Network candidates before setup.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const manufacturer = candidateArg.manufacturer?.toLowerCase() || '';
    const model = candidateArg.model?.toLowerCase() || '';
    const name = candidateArg.name?.toLowerCase() || '';
    const matched = candidateArg.integrationDomain === 'unifi' || manufacturer.includes('ubiquiti') && unifiModels.some((modelArg) => `${model} ${name}`.includes(modelArg));
    return {
      matched,
      confidence: matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has UniFi Network metadata.' : 'Candidate is not UniFi Network.',
      candidate: matched ? { ...candidateArg, port: candidateArg.port || unifiDefaultPort } : undefined,
      normalizedDeviceId: candidateArg.id || candidateArg.macAddress,
    };
  }
}

export const createUnifiDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({
    integrationDomain: 'unifi',
    displayName: 'UniFi Network',
  })
    .addMatcher(new UnifiMdnsMatcher())
    .addMatcher(new UnifiSsdpMatcher())
    .addMatcher(new UnifiManualMatcher())
    .addMatcher(new UnifiDiscoveryDeviceMatcher())
    .addValidator(new UnifiCandidateValidator());
};
