import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IFrontierSiliconManualEntry, IFrontierSiliconSsdpRecord, TFrontierSiliconProtocol } from './frontier_silicon.types.js';
import { frontierSiliconDefaultPort, frontierSiliconSsdpSt } from './frontier_silicon.types.js';

const frontierSiliconDomain = 'frontier_silicon';
const frontierSiliconNames = ['frontier silicon', 'frontier_silicon', 'frontier-silicon', 'undok', 'fsapi', 'hama', 'medion', 'auna'];

export class FrontierSiliconSsdpMatcher implements IDiscoveryMatcher<IFrontierSiliconSsdpRecord> {
  public id = 'frontier_silicon-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize Frontier Silicon UNDOK FSAPI SSDP advertisements.';

  public async matches(recordArg: IFrontierSiliconSsdpRecord): Promise<IDiscoveryMatch> {
    const st = header(recordArg, 'st') || upnp(recordArg, 'deviceType');
    const usn = header(recordArg, 'usn');
    const location = header(recordArg, 'location') || recordArg.ssdpLocation;
    const speakerName = header(recordArg, 'speaker-name') || header(recordArg, 'SPEAKER-NAME');
    const friendlyName = upnp(recordArg, 'friendlyName') || speakerName;
    const manufacturer = upnp(recordArg, 'manufacturer') || 'Frontier Silicon';
    const model = upnp(recordArg, 'modelName') || upnp(recordArg, 'model');
    const haystack = `${st || ''} ${usn || ''} ${friendlyName || ''} ${manufacturer || ''} ${model || ''}`.toLowerCase();
    const exactMatch = normalizeUrn(st) === normalizeUrn(frontierSiliconSsdpSt);
    const hintMatch = haystack.includes('frontier-silicon-com') || haystack.includes('undok') || haystack.includes('fsapi');

    if (!exactMatch && !hintMatch) {
      return { matched: false, confidence: 'low', reason: 'SSDP record is not a Frontier Silicon FSAPI advertisement.' };
    }

    const url = parseUrl(location);
    const host = url?.hostname;
    const port = urlPort(url) || frontierSiliconDefaultPort;
    const id = stripUuid(usn) || (host ? `${host}:${port}` : friendlyName);
    return {
      matched: true,
      confidence: exactMatch && host ? 'certain' : host ? 'high' : 'medium',
      reason: exactMatch ? 'SSDP ST matches Frontier Silicon FSAPI.' : 'SSDP metadata contains Frontier Silicon FSAPI hints.',
      normalizedDeviceId: id,
      candidate: {
        source: 'ssdp',
        integrationDomain: frontierSiliconDomain,
        id,
        host,
        port,
        name: friendlyName,
        manufacturer,
        model,
        metadata: {
          st,
          usn,
          location,
          deviceUrl: location,
          speakerName,
          protocol: protocolFromUrl(url),
          frontierSilicon: true,
        },
      },
      metadata: { st, location, speakerName },
    };
  }
}

export class FrontierSiliconManualMatcher implements IDiscoveryMatcher<IFrontierSiliconManualEntry> {
  public id = 'frontier_silicon-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Frontier Silicon FSAPI setup entries.';

  public async matches(inputArg: IFrontierSiliconManualEntry): Promise<IDiscoveryMatch> {
    const webfsapiUrl = inputArg.webfsapiUrl || stringMetadata(inputArg.metadata?.webfsapiUrl);
    const deviceUrl = inputArg.deviceUrl || stringMetadata(inputArg.metadata?.deviceUrl);
    const haystack = `${inputArg.name || ''} ${inputArg.manufacturer || ''} ${inputArg.model || ''} ${webfsapiUrl || ''} ${deviceUrl || ''}`.toLowerCase();
    const matched = Boolean(inputArg.host || webfsapiUrl || deviceUrl || inputArg.metadata?.frontierSilicon || inputArg.metadata?.frontier_silicon || frontierSiliconNames.some((needleArg) => haystack.includes(needleArg)));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Frontier Silicon setup hints.' };
    }

    const endpointUrl = parseUrl(webfsapiUrl) || parseUrl(deviceUrl);
    const host = inputArg.host || endpointUrl?.hostname;
    const port = inputArg.port || urlPort(endpointUrl) || frontierSiliconDefaultPort;
    const id = inputArg.radioId || inputArg.id || (host ? `${host}:${port}` : webfsapiUrl || deviceUrl);
    return {
      matched: true,
      confidence: host || webfsapiUrl ? 'high' : 'medium',
      reason: 'Manual entry can start Frontier Silicon setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: frontierSiliconDomain,
        id,
        host,
        port,
        name: inputArg.name,
        manufacturer: inputArg.manufacturer || 'Frontier Silicon',
        model: inputArg.model,
        metadata: {
          ...inputArg.metadata,
          protocol: inputArg.protocol || protocolFromUrl(endpointUrl),
          pin: inputArg.pin ? true : undefined,
          deviceUrl,
          webfsapiUrl,
          frontierSilicon: true,
        },
      },
    };
  }
}

export class FrontierSiliconCandidateValidator implements IDiscoveryValidator {
  public id = 'frontier_silicon-candidate-validator';
  public description = 'Validate Frontier Silicon candidates have local FSAPI setup metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const st = stringMetadata(metadata.st);
    const webfsapiUrl = stringMetadata(metadata.webfsapiUrl);
    const deviceUrl = stringMetadata(metadata.deviceUrl) || stringMetadata(metadata.location);
    const haystack = `${candidateArg.name || ''} ${candidateArg.manufacturer || ''} ${candidateArg.model || ''} ${webfsapiUrl || ''} ${deviceUrl || ''}`.toLowerCase();
    const matched = candidateArg.integrationDomain === frontierSiliconDomain
      || Boolean(metadata.frontierSilicon || metadata.frontier_silicon)
      || normalizeUrn(st) === normalizeUrn(frontierSiliconSsdpSt)
      || frontierSiliconNames.some((needleArg) => haystack.includes(needleArg));

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Candidate is not Frontier Silicon.' };
    }
    if (!candidateArg.host && !webfsapiUrl && !deviceUrl) {
      return { matched: false, confidence: 'medium', reason: 'Frontier Silicon candidate lacks host, device URL, or webfsapi URL.' };
    }

    const endpointUrl = parseUrl(webfsapiUrl) || parseUrl(deviceUrl);
    const port = candidateArg.port || urlPort(endpointUrl) || frontierSiliconDefaultPort;
    return {
      matched: true,
      confidence: candidateArg.id ? 'certain' : 'high',
      reason: 'Candidate has Frontier Silicon FSAPI metadata.',
      normalizedDeviceId: candidateArg.id || (candidateArg.host ? `${candidateArg.host}:${port}` : webfsapiUrl || deviceUrl),
      candidate: {
        ...candidateArg,
        port,
        metadata: {
          ...metadata,
          protocol: stringMetadata(metadata.protocol) || protocolFromUrl(endpointUrl),
        },
      },
    };
  }
}

export const createFrontierSiliconDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: frontierSiliconDomain, displayName: 'Frontier Silicon' })
    .addMatcher(new FrontierSiliconSsdpMatcher())
    .addMatcher(new FrontierSiliconManualMatcher())
    .addValidator(new FrontierSiliconCandidateValidator());
};

const header = (recordArg: IFrontierSiliconSsdpRecord, keyArg: string): string | undefined => {
  return recordArg[keyArg as keyof IFrontierSiliconSsdpRecord] as string | undefined
    || valueForKey(recordArg.headers, keyArg)
    || valueForKey(recordArg.ssdpHeaders, keyArg);
};

const upnp = (recordArg: IFrontierSiliconSsdpRecord, keyArg: string): string | undefined => {
  return valueForKey(recordArg.upnp, keyArg) || valueForKey(recordArg.headers, keyArg) || valueForKey(recordArg.ssdpHeaders, keyArg);
};

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

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

const stripUuid = (valueArg: string | undefined): string | undefined => valueArg?.replace(/^uuid:/i, '').split('::')[0];
const normalizeUrn = (valueArg: string | undefined): string => (valueArg || '').toLowerCase();
const urlPort = (urlArg: URL | undefined): number | undefined => urlArg?.port ? Number(urlArg.port) : undefined;
const protocolFromUrl = (urlArg: URL | undefined): TFrontierSiliconProtocol | undefined => urlArg?.protocol === 'https:' ? 'https' : urlArg?.protocol === 'http:' ? 'http' : undefined;
const stringMetadata = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg ? valueArg : undefined;
