import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { ISamsungtvManualEntry, ISamsungtvMdnsRecord, ISamsungtvSsdpRecord } from './samsungtv.types.js';

const remoteControlReceiver = 'urn:samsung.com:device:RemoteControlReceiver:1';
const mainTvAgent = 'urn:samsung.com:service:MainTVAgent2:1';
const renderingControl = 'urn:schemas-upnp-org:service:RenderingControl:1';

export class SamsungtvSsdpMatcher implements IDiscoveryMatcher<ISamsungtvSsdpRecord> {
  public id = 'samsungtv-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize Samsung TV SSDP and UPnP advertisements.';

  public async matches(recordArg: ISamsungtvSsdpRecord): Promise<IDiscoveryMatch> {
    const st = readRecordValue(recordArg, 'st', 'ST', 'ssdp_st') || recordArg.ssdp_st;
    const usn = readRecordValue(recordArg, 'usn', 'USN', 'udn', 'UDN', 'ssdp_usn') || recordArg.ssdp_usn;
    const location = readRecordValue(recordArg, 'location', 'LOCATION', 'ssdp_location') || recordArg.ssdp_location;
    const manufacturer = readRecordValue(recordArg, 'manufacturer', 'MANUFACTURER', 'upnp:manufacturer') || '';
    const model = readRecordValue(recordArg, 'modelName', 'model_name', 'model', 'upnp:modelName');
    const matchedByService = st === remoteControlReceiver || st === mainTvAgent;
    const matchedByRendering = st === renderingControl && startsSamsung(manufacturer);
    if (!matchedByService && !matchedByRendering) {
      return { matched: false, confidence: 'low', reason: 'SSDP record is not a Samsung TV advertisement.' };
    }

    const url = safeUrl(location);
    const id = stripUuid(usn || readRecordValue(recordArg, 'udn', 'UDN'));
    const metadata: Record<string, unknown> = { st, usn, location };
    if (st === renderingControl && location) {
      metadata.ssdpRenderingControlLocation = location;
    }
    if (st === mainTvAgent && location) {
      metadata.ssdpMainTvAgentLocation = location;
    }

    return {
      matched: true,
      confidence: id ? 'certain' : 'high',
      reason: 'SSDP record matches Samsung TV metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'ssdp',
        integrationDomain: 'samsungtv',
        id,
        host: url?.hostname,
        port: 8001,
        manufacturer: manufacturer || 'Samsung',
        model,
        metadata,
      },
    };
  }
}

export class SamsungtvMdnsMatcher implements IDiscoveryMatcher<ISamsungtvMdnsRecord> {
  public id = 'samsungtv-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize Samsung TV mDNS AirPlay advertisements.';

  public async matches(recordArg: ISamsungtvMdnsRecord): Promise<IDiscoveryMatch> {
    const txt = { ...(recordArg.txt ?? {}), ...(recordArg.properties ?? {}) };
    const type = recordArg.type?.toLowerCase() || '';
    const manufacturer = txt.manufacturer || txt.Manufacturer || '';
    const name = recordArg.name || txt.name || txt.friendlyName;
    const model = txt.model || txt.modelName || txt.modelid;
    const matched = type === '_airplay._tcp.local.' && startsSamsung(manufacturer);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record is not a Samsung TV AirPlay advertisement.' };
    }

    const id = txt.deviceid || txt.deviceId || txt.id || name;
    return {
      matched: true,
      confidence: id ? 'certain' : 'high',
      reason: 'mDNS record matches Samsung TV AirPlay metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: 'samsungtv',
        id,
        host: recordArg.host,
        port: 8001,
        name,
        manufacturer: 'Samsung',
        model,
        macAddress: txt.deviceid || txt.deviceId,
        metadata: { mdnsType: recordArg.type, mdnsName: recordArg.name, mdnsPort: recordArg.port, txt },
      },
    };
  }
}

export class SamsungtvManualMatcher implements IDiscoveryMatcher<ISamsungtvManualEntry> {
  public id = 'samsungtv-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Samsung TV setup entries.';

  public async matches(inputArg: ISamsungtvManualEntry): Promise<IDiscoveryMatch> {
    const manufacturer = inputArg.manufacturer?.toLowerCase() || '';
    const model = inputArg.model?.toLowerCase() || '';
    const matched = Boolean(inputArg.host || startsSamsung(manufacturer) || model.includes('samsung') || model.includes('tizen') || inputArg.metadata?.samsungtv);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Samsung TV setup hints.' };
    }

    return {
      matched: true,
      confidence: inputArg.host ? 'high' : 'medium',
      reason: 'Manual entry can start Samsung TV setup.',
      normalizedDeviceId: inputArg.id || inputArg.macAddress,
      candidate: {
        source: 'manual',
        integrationDomain: 'samsungtv',
        id: inputArg.id || inputArg.macAddress,
        host: inputArg.host,
        port: inputArg.port || 8001,
        name: inputArg.name,
        manufacturer: inputArg.manufacturer || 'Samsung',
        model: inputArg.model,
        macAddress: inputArg.macAddress,
        metadata: { ...(inputArg.metadata ?? {}), token: inputArg.token },
      },
    };
  }
}

export class SamsungtvCandidateValidator implements IDiscoveryValidator {
  public id = 'samsungtv-candidate-validator';
  public description = 'Validate Samsung TV discovery candidates.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const manufacturer = candidateArg.manufacturer?.toLowerCase() || '';
    const model = candidateArg.model?.toLowerCase() || '';
    const matched = candidateArg.integrationDomain === 'samsungtv'
      || startsSamsung(manufacturer)
      || model.includes('samsung')
      || model.includes('tizen')
      || Boolean(candidateArg.metadata?.samsungtv);
    return {
      matched,
      confidence: matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Samsung TV metadata.' : 'Candidate is not Samsung TV.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id || candidateArg.macAddress,
    };
  }
}

export const createSamsungtvDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'samsungtv', displayName: 'Samsung Smart TV' })
    .addMatcher(new SamsungtvSsdpMatcher())
    .addMatcher(new SamsungtvMdnsMatcher())
    .addMatcher(new SamsungtvManualMatcher())
    .addValidator(new SamsungtvCandidateValidator());
};

const startsSamsung = (valueArg: string | undefined): boolean => Boolean(valueArg?.toLowerCase().startsWith('samsung'));

const stripUuid = (valueArg?: string): string | undefined => {
  if (!valueArg) {
    return undefined;
  }
  return valueArg.replace(/^uuid:/i, '').split('::')[0];
};

const safeUrl = (valueArg?: string): URL | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    return new URL(valueArg);
  } catch {
    return undefined;
  }
};

const readRecordValue = (recordArg: ISamsungtvSsdpRecord, ...keysArg: string[]): string | undefined => {
  const maps = [recordArg.headers, recordArg.upnp, recordArg as Record<string, string | undefined>].filter(Boolean) as Array<Record<string, string | undefined>>;
  for (const key of keysArg) {
    const lowerKey = key.toLowerCase();
    for (const map of maps) {
      for (const [candidateKey, value] of Object.entries(map)) {
        if (candidateKey.toLowerCase() === lowerKey && value) {
          return value;
        }
      }
    }
  }
  return undefined;
};
