import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { androidtvRemoteApiPort, androidtvRemoteMdnsService, androidtvRemotePairPort } from './androidtv_remote.constants.js';
import type { IAndroidtvRemoteManualEntry, IAndroidtvRemoteMdnsRecord } from './androidtv_remote.types.js';

export class AndroidtvRemoteMdnsMatcher implements IDiscoveryMatcher<IAndroidtvRemoteMdnsRecord> {
  public id = 'androidtv-remote-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize Android TV Remote protocol v2 mDNS advertisements.';

  public async matches(recordArg: IAndroidtvRemoteMdnsRecord): Promise<IDiscoveryMatch> {
    const type = this.stringValue(recordArg.type || recordArg.metadata?.type).toLowerCase();
    const name = this.stringValue(recordArg.name || recordArg.metadata?.name);
    const matched = type.includes('androidtvremote2') || name.toLowerCase().includes(androidtvRemoteMdnsService);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not an Android TV Remote advertisement.' };
    }
    const properties = { ...(recordArg.txt || {}), ...(recordArg.properties || {}) };
    const macAddress = this.stringValue(properties.bt || properties.mac || properties.macAddress || recordArg.metadata?.macAddress);
    const id = this.stringValue(properties.id || macAddress);
    const displayName = this.displayName(name);
    const apiPort = this.numberValue(recordArg.port) || androidtvRemoteApiPort;
    return {
      matched: true,
      confidence: recordArg.host && macAddress ? 'certain' : recordArg.host ? 'high' : 'medium',
      reason: 'mDNS record matches Android TV Remote protocol v2.',
      normalizedDeviceId: id || recordArg.host,
      candidate: {
        source: 'mdns',
        integrationDomain: 'androidtv_remote',
        id,
        host: recordArg.host,
        port: apiPort,
        name: displayName,
        macAddress,
        metadata: {
          type: recordArg.type,
          txt: properties,
          apiPort,
          pairPort: androidtvRemotePairPort,
        },
      },
    };
  }

  private displayName(nameArg: string): string | undefined {
    const name = nameArg.replace(androidtvRemoteMdnsService, '').replace(/\.$/, '').trim();
    return name || undefined;
  }

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

  private numberValue(valueArg: unknown): number | undefined {
    return typeof valueArg === 'number' && Number.isFinite(valueArg) && valueArg > 0 ? valueArg : undefined;
  }
}

export class AndroidtvRemoteManualMatcher implements IDiscoveryMatcher<IAndroidtvRemoteManualEntry> {
  public id = 'androidtv-remote-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Android TV Remote host entries.';

  public async matches(inputArg: IAndroidtvRemoteManualEntry): Promise<IDiscoveryMatch> {
    const matched = Boolean(inputArg.host || inputArg.metadata?.androidtvRemote || inputArg.metadata?.androidtv_remote);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not include an Android TV Remote host.' };
    }
    const apiPort = this.numberValue(inputArg.apiPort || inputArg.port || inputArg.metadata?.apiPort) || androidtvRemoteApiPort;
    const pairPort = this.numberValue(inputArg.pairPort || inputArg.metadata?.pairPort) || androidtvRemotePairPort;
    return {
      matched: true,
      confidence: inputArg.host ? 'high' : 'medium',
      reason: 'Manual entry can start Android TV Remote setup.',
      normalizedDeviceId: inputArg.id || inputArg.macAddress || inputArg.host,
      candidate: {
        source: 'manual',
        integrationDomain: 'androidtv_remote',
        id: inputArg.id || inputArg.macAddress,
        host: inputArg.host,
        port: apiPort,
        name: inputArg.deviceName || inputArg.name,
        manufacturer: inputArg.manufacturer,
        model: inputArg.model,
        macAddress: inputArg.macAddress,
        metadata: {
          ...inputArg.metadata,
          apiPort,
          pairPort,
          enableIme: inputArg.enableIme,
        },
      },
    };
  }

  private numberValue(valueArg: unknown): number | undefined {
    return typeof valueArg === 'number' && Number.isFinite(valueArg) && valueArg > 0 ? valueArg : undefined;
  }
}

export class AndroidtvRemoteCandidateValidator implements IDiscoveryValidator {
  public id = 'androidtv-remote-candidate-validator';
  public description = 'Validate Android TV Remote candidates.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const matched = candidateArg.integrationDomain === 'androidtv_remote' || Boolean(candidateArg.metadata?.androidtvRemote || candidateArg.metadata?.androidtv_remote);
    return {
      matched,
      confidence: matched && candidateArg.host && (candidateArg.id || candidateArg.macAddress) ? 'certain' : matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Android TV Remote metadata.' : 'Candidate is not Android TV Remote.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id || candidateArg.macAddress || candidateArg.host,
      metadata: matched ? {
        apiPort: candidateArg.port || candidateArg.metadata?.apiPort || androidtvRemoteApiPort,
        pairPort: candidateArg.metadata?.pairPort || androidtvRemotePairPort,
      } : undefined,
    };
  }
}

export const createAndroidtvRemoteDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'androidtv_remote', displayName: 'Android TV Remote' })
    .addMatcher(new AndroidtvRemoteMdnsMatcher())
    .addMatcher(new AndroidtvRemoteManualMatcher())
    .addValidator(new AndroidtvRemoteCandidateValidator());
};
