import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { INanoleafManualEntry, INanoleafMdnsRecord, INanoleafSsdpRecord } from './nanoleaf.types.js';

const DEFAULT_NANOLEAF_PORT = 16021;
const NANOLEAF_MDNS_TYPES = new Set(['_nanoleafapi._tcp.local.', '_nanoleafms._tcp.local.']);
const NANOLEAF_SSDP_TYPES = new Set([
  'nanoleaf_aurora:light',
  'nanoleaf:nl29',
  'nanoleaf:nl42',
  'nanoleaf:nl52',
  'nanoleaf:nl69',
  'inanoleaf:nl81',
]);

export class NanoleafMdnsMatcher implements IDiscoveryMatcher<INanoleafMdnsRecord> {
  public id = 'nanoleaf-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize Nanoleaf zeroconf records for _nanoleafapi._tcp and _nanoleafms._tcp.';

  public async matches(recordArg: INanoleafMdnsRecord): Promise<IDiscoveryMatch> {
    const type = recordArg.type?.toLowerCase() || '';
    const name = recordArg.name?.toLowerCase() || '';
    const matched = NANOLEAF_MDNS_TYPES.has(type) || name.includes('nanoleaf') && (type.includes('nanoleaf') || type === '_http._tcp.local.');
    if (!matched) {
      return {
        matched: false,
        confidence: 'low',
        reason: 'mDNS record is not a Nanoleaf advertisement.',
      };
    }

    const deviceId = this.txtValue(recordArg.txt, 'id', 'deviceid', 'nl-deviceid') || recordArg.name;
    return {
      matched: true,
      confidence: deviceId ? 'certain' : 'high',
      reason: 'mDNS record matches Nanoleaf zeroconf metadata.',
      normalizedDeviceId: deviceId,
      candidate: {
        source: 'mdns',
        integrationDomain: 'nanoleaf',
        id: deviceId,
        host: recordArg.host,
        port: recordArg.port || DEFAULT_NANOLEAF_PORT,
        name: recordArg.name,
        manufacturer: 'Nanoleaf',
        model: this.txtValue(recordArg.txt, 'model', 'modelid', 'md'),
        metadata: {
          mdnsName: recordArg.name,
          mdnsType: recordArg.type,
          txt: recordArg.txt,
        },
      },
    };
  }

  private txtValue(txtArg: Record<string, string | undefined> | undefined, ...keysArg: string[]): string | undefined {
    if (!txtArg) {
      return undefined;
    }
    const wanted = new Set(keysArg.map((keyArg) => keyArg.toLowerCase()));
    for (const [key, value] of Object.entries(txtArg)) {
      if (value !== undefined && wanted.has(key.toLowerCase())) {
        return value;
      }
    }
    return undefined;
  }
}

export class NanoleafSsdpMatcher implements IDiscoveryMatcher<INanoleafSsdpRecord> {
  public id = 'nanoleaf-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize Nanoleaf SSDP responses by ST and nl-* headers.';

  public async matches(recordArg: INanoleafSsdpRecord): Promise<IDiscoveryMatch> {
    const st = (recordArg.st || this.header(recordArg.headers, 'st') || '').toLowerCase();
    const matched = NANOLEAF_SSDP_TYPES.has(st) || st.includes('nanoleaf') || Boolean(this.header(recordArg.headers, 'nl-deviceid'));
    if (!matched) {
      return {
        matched: false,
        confidence: 'low',
        reason: 'SSDP response is not a Nanoleaf advertisement.',
      };
    }

    const hostPort = this.parseHostPort(this.header(recordArg.headers, '_host') || recordArg.location);
    const deviceId = this.header(recordArg.headers, 'nl-deviceid') || recordArg.usn;
    return {
      matched: true,
      confidence: deviceId ? 'certain' : 'high',
      reason: 'SSDP response matches Nanoleaf metadata.',
      normalizedDeviceId: deviceId,
      candidate: {
        source: 'ssdp',
        integrationDomain: 'nanoleaf',
        id: deviceId,
        host: hostPort.host,
        port: hostPort.port || DEFAULT_NANOLEAF_PORT,
        name: this.header(recordArg.headers, 'nl-devicename'),
        manufacturer: 'Nanoleaf',
        model: st.startsWith('nanoleaf:') || st.startsWith('inanoleaf:') ? st.split(':')[1]?.toUpperCase() : undefined,
        metadata: {
          st: recordArg.st,
          usn: recordArg.usn,
          location: recordArg.location,
          headers: recordArg.headers,
        },
      },
    };
  }

  private header(headersArg: Record<string, string | undefined> | undefined, keyArg: string): string | undefined {
    if (!headersArg) {
      return undefined;
    }
    const wanted = keyArg.toLowerCase();
    for (const [key, value] of Object.entries(headersArg)) {
      if (key.toLowerCase() === wanted) {
        return value;
      }
    }
    return undefined;
  }

  private parseHostPort(valueArg: string | undefined): { host?: string; port?: number } {
    if (!valueArg) {
      return {};
    }
    try {
      const url = new URL(valueArg.includes('://') ? valueArg : `http://${valueArg}`);
      return {
        host: url.hostname,
        port: url.port ? Number(url.port) : undefined,
      };
    } catch {
      const [host, port] = valueArg.split(':');
      return { host, port: port ? Number(port) : undefined };
    }
  }
}

export class NanoleafManualMatcher implements IDiscoveryMatcher<INanoleafManualEntry> {
  public id = 'nanoleaf-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Nanoleaf setup entries by host or Nanoleaf metadata.';

  public async matches(inputArg: INanoleafManualEntry): Promise<IDiscoveryMatch> {
    const manufacturer = inputArg.manufacturer?.toLowerCase() || '';
    const model = inputArg.model?.toLowerCase() || '';
    const name = inputArg.name?.toLowerCase() || '';
    const matched = Boolean(inputArg.host || manufacturer.includes('nanoleaf') || model.startsWith('nl') || model.includes('nanoleaf') || name.includes('nanoleaf') || inputArg.metadata?.nanoleaf);
    if (!matched) {
      return {
        matched: false,
        confidence: 'low',
        reason: 'Manual entry does not contain Nanoleaf setup hints.',
      };
    }
    return {
      matched: true,
      confidence: inputArg.host ? 'high' : 'medium',
      reason: 'Manual entry can start Nanoleaf setup.',
      normalizedDeviceId: inputArg.id,
      candidate: {
        source: 'manual',
        integrationDomain: 'nanoleaf',
        id: inputArg.id,
        host: inputArg.host,
        port: inputArg.port || DEFAULT_NANOLEAF_PORT,
        name: inputArg.name,
        manufacturer: 'Nanoleaf',
        model: inputArg.model,
        metadata: inputArg.metadata,
      },
    };
  }
}

export class NanoleafCandidateValidator implements IDiscoveryValidator {
  public id = 'nanoleaf-candidate-validator';
  public description = 'Validate candidate metadata before starting Nanoleaf 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 === 'nanoleaf' || manufacturer.includes('nanoleaf') || model.startsWith('nl') || model.includes('nanoleaf') || name.includes('nanoleaf');
    return {
      matched,
      confidence: matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Nanoleaf metadata.' : 'Candidate is not Nanoleaf.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id,
    };
  }
}

export const createNanoleafDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({
    integrationDomain: 'nanoleaf',
    displayName: 'Nanoleaf',
  })
    .addMatcher(new NanoleafMdnsMatcher())
    .addMatcher(new NanoleafSsdpMatcher())
    .addMatcher(new NanoleafManualMatcher())
    .addValidator(new NanoleafCandidateValidator());
};
