import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IHomekitManualEntry, IHomekitMdnsRecord } from './homekit_controller.types.js';

const hapMdnsTypes = new Set(['_hap._tcp.local', '_hap._udp.local']);

const categoryNames: Record<number, string> = {
  1: 'Other',
  2: 'Bridge',
  3: 'Fan',
  4: 'Garage Door Opener',
  5: 'Lightbulb',
  6: 'Door Lock',
  7: 'Outlet',
  8: 'Switch',
  9: 'Thermostat',
  10: 'Sensor',
  17: 'IP Camera',
  19: 'Air Purifier',
  20: 'Heater',
  21: 'Air Conditioner',
  22: 'Humidifier',
  23: 'Dehumidifier',
  24: 'Sprinkler',
  26: 'Window Covering',
  28: 'Security System',
};

export class HomekitControllerMdnsMatcher implements IDiscoveryMatcher<IHomekitMdnsRecord> {
  public id = 'homekit-controller-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize HomeKit Accessory Protocol mDNS advertisements.';

  public async matches(recordArg: IHomekitMdnsRecord): Promise<IDiscoveryMatch> {
    const type = this.normalizeType(recordArg.type);
    const txt = this.normalizedTxt(recordArg);
    const id = this.stringValue(txt.id);
    const model = this.stringValue(txt.md);
    const manufacturer = this.stringValue(txt.mf);
    const category = this.numberValue(txt.ci);
    const matched = hapMdnsTypes.has(type) || this.hasHapTxt(txt);

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a HomeKit Accessory Protocol advertisement.' };
    }

    const statusFlags = this.numberValue(txt.sf);
    const paired = statusFlags === undefined ? undefined : (statusFlags & 1) === 0;
    const configNumber = this.numberValue(txt['c#']);
    const stateNumber = this.numberValue(txt['s#']);
    const host = recordArg.host || recordArg.hostname || recordArg.addresses?.[0];
    const name = this.serviceName(recordArg, txt);

    return {
      matched: true,
      confidence: id ? 'certain' : 'high',
      reason: 'mDNS record matches HomeKit Accessory Protocol metadata.',
      normalizedDeviceId: this.normalizeId(id),
      candidate: {
        source: 'mdns',
        integrationDomain: 'homekit_controller',
        id: this.normalizeId(id),
        host,
        port: recordArg.port || 51826,
        name,
        manufacturer,
        model,
        metadata: {
          homekit: true,
          mdnsName: recordArg.name,
          mdnsType: recordArg.type,
          txt,
          id,
          category,
          categoryName: category === undefined ? undefined : categoryNames[category] || `Category ${category}`,
          paired,
          statusFlags,
          configNumber,
          stateNumber,
          setupHash: txt.sh,
          protocolVersion: txt.pv,
        },
      },
    };
  }

  private normalizedTxt(recordArg: IHomekitMdnsRecord): Record<string, string | undefined> {
    const source = recordArg.txt || recordArg.properties || {};
    const txt: Record<string, string | undefined> = {};
    for (const [key, value] of Object.entries(source)) {
      txt[key.toLowerCase()] = this.txtValue(value);
    }
    return txt;
  }

  private hasHapTxt(txtArg: Record<string, string | undefined>): boolean {
    return Boolean(
      this.stringValue(txtArg.id)
        && (txtArg.sf !== undefined
          || txtArg.ci !== undefined
          || txtArg['c#'] !== undefined
          || txtArg['s#'] !== undefined
          || txtArg.sh !== undefined
          || txtArg.pv !== undefined
          || txtArg.md !== undefined)
    );
  }

  private txtValue(valueArg: unknown): string | undefined {
    if (typeof valueArg === 'string') {
      return valueArg;
    }
    if (typeof valueArg === 'number' || typeof valueArg === 'boolean') {
      return String(valueArg);
    }
    if (Array.isArray(valueArg)) {
      return this.txtValue(valueArg[0]);
    }
    if (valueArg instanceof Uint8Array) {
      return new TextDecoder().decode(valueArg);
    }
    return undefined;
  }

  private serviceName(recordArg: IHomekitMdnsRecord, txtArg: Record<string, string | undefined>): string | undefined {
    const name = recordArg.name?.replace(/\._hap\._(?:tcp|udp)\.local\.?$/i, '');
    return this.stringValue(txtArg.name) || name;
  }

  private normalizeType(valueArg?: string): string {
    return (valueArg || '').toLowerCase().replace(/\.$/, '');
  }

  private normalizeId(valueArg?: string): string | undefined {
    return valueArg?.trim().toLowerCase();
  }

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

  private numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg === 'string' && valueArg.trim()) {
      const value = Number.parseInt(valueArg, 10);
      return Number.isFinite(value) ? value : undefined;
    }
    return undefined;
  }
}

export class HomekitControllerManualMatcher implements IDiscoveryMatcher<IHomekitManualEntry> {
  public id = 'homekit-controller-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual HomeKit Controller setup entries.';

  public async matches(inputArg: IHomekitManualEntry): Promise<IDiscoveryMatch> {
    const pairingId = inputArg.pairingData?.AccessoryPairingID;
    const snapshot = inputArg.snapshot;
    const accessories = inputArg.accessories || snapshot?.accessories;
    const setupCode = this.stringValue(inputArg.setupCode) || this.stringValue(inputArg.metadata?.setupCode);
    const model = inputArg.model?.toLowerCase() || '';
    const manufacturer = inputArg.manufacturer?.toLowerCase() || '';
    const matched = Boolean(
      inputArg.host
        || setupCode
        || inputArg.pairingData
        || accessories?.length
        || inputArg.metadata?.homekit
        || manufacturer.includes('homekit')
        || model.includes('homekit')
    );

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

    return {
      matched: true,
      confidence: pairingId || accessories?.length || setupCode ? 'high' : 'medium',
      reason: 'Manual entry can start HomeKit Controller setup.',
      normalizedDeviceId: this.normalizeId(inputArg.id || pairingId || snapshot?.id),
      candidate: {
        source: 'manual',
        integrationDomain: 'homekit_controller',
        id: this.normalizeId(inputArg.id || pairingId || snapshot?.id),
        host: inputArg.host || inputArg.pairingData?.AccessoryIP || snapshot?.host,
        port: inputArg.port || inputArg.pairingData?.AccessoryPort || snapshot?.port || 51826,
        name: inputArg.name || snapshot?.name,
        manufacturer: inputArg.manufacturer,
        model: inputArg.model,
        metadata: {
          ...inputArg.metadata,
          homekit: true,
          category: inputArg.category,
          categoryName: typeof inputArg.category === 'number' ? categoryNames[inputArg.category] : undefined,
          pairingData: inputArg.pairingData,
          accessories,
          snapshot,
          setupCode,
          setupCodeProvided: Boolean(setupCode),
        },
      },
    };
  }

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

  private normalizeId(valueArg?: string): string | undefined {
    return valueArg?.trim().toLowerCase();
  }
}

export class HomekitControllerCandidateValidator implements IDiscoveryValidator {
  public id = 'homekit-controller-candidate-validator';
  public description = 'Validate HomeKit Controller candidate metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const model = candidateArg.model?.toLowerCase() || '';
    const manufacturer = candidateArg.manufacturer?.toLowerCase() || '';
    const metadata = candidateArg.metadata || {};
    const mdnsType = typeof metadata.mdnsType === 'string' ? metadata.mdnsType.toLowerCase().replace(/\.$/, '') : '';
    const id = typeof metadata.id === 'string' ? metadata.id : candidateArg.id;
    const category = this.numberValue(metadata.category);
    const hasHomekitMetadata = this.hasHomekitMetadata(metadata, mdnsType, id);
    const matched = Boolean(
      candidateArg.integrationDomain === 'homekit_controller'
        || hasHomekitMetadata
        || manufacturer.includes('homekit')
        || model.includes('homekit')
    );

    return {
      matched,
      confidence: matched && (candidateArg.host || id || metadata.pairingData || metadata.snapshot) ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has HomeKit metadata.' : 'Candidate is not HomeKit.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: id?.toLowerCase(),
      metadata: matched ? { categoryName: category === undefined ? undefined : categoryNames[category] } : undefined,
    };
  }

  private hasHomekitMetadata(metadataArg: Record<string, unknown>, mdnsTypeArg: string, idArg?: string): boolean {
    return Boolean(
      metadataArg.homekit === true
        || hapMdnsTypes.has(mdnsTypeArg)
        || metadataArg.pairingData
        || metadataArg.snapshot
        || metadataArg.accessories
        || metadataArg.setupCode
        || metadataArg.setupCodeProvided
        || metadataArg.statusFlags !== undefined
        || metadataArg.setupHash
        || metadataArg.protocolVersion
        || metadataArg.configNumber !== undefined
        || metadataArg.stateNumber !== undefined
        || (this.looksLikeHomekitId(idArg) && (metadataArg.category !== undefined || metadataArg.txt !== undefined))
    );
  }

  private looksLikeHomekitId(valueArg?: string): boolean {
    return typeof valueArg === 'string' && /^[0-9a-f]{2}(?::[0-9a-f]{2}){5}$/i.test(valueArg.trim());
  }

  private numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg === 'string' && valueArg.trim()) {
      const value = Number.parseInt(valueArg, 10);
      return Number.isFinite(value) ? value : undefined;
    }
    return undefined;
  }
}

export const createHomekitControllerDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'homekit_controller', displayName: 'HomeKit Device' })
    .addMatcher(new HomekitControllerMdnsMatcher())
    .addMatcher(new HomekitControllerManualMatcher())
    .addValidator(new HomekitControllerCandidateValidator());
};
