import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IJellyfinManualEntry, IJellyfinMdnsRecord, IJellyfinSsdpRecord } from './jellyfin.types.js';

const defaultHttpPort = 8096;
const defaultHttpsPort = 8920;

export class JellyfinManualMatcher implements IDiscoveryMatcher<IJellyfinManualEntry> {
  public id = 'jellyfin-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manually supplied Jellyfin server endpoints.';

  public async matches(inputArg: IJellyfinManualEntry): Promise<IDiscoveryMatch> {
    const parsedUrl = parseUrl(inputArg.url);
    const haystack = `${inputArg.name || ''} ${inputArg.model || ''} ${inputArg.manufacturer || ''}`.toLowerCase();
    const matched = Boolean(inputArg.host || parsedUrl || inputArg.metadata?.jellyfin || haystack.includes('jellyfin'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Jellyfin setup hints.' };
    }
    const ssl = inputArg.ssl ?? parsedUrl?.ssl ?? false;
    return {
      matched: true,
      confidence: inputArg.host || parsedUrl ? 'high' : 'medium',
      reason: 'Manual entry can start Jellyfin setup.',
      normalizedDeviceId: inputArg.id,
      candidate: {
        source: 'manual',
        integrationDomain: 'jellyfin',
        id: inputArg.id,
        host: inputArg.host || parsedUrl?.host,
        port: inputArg.port || parsedUrl?.port || (ssl ? defaultHttpsPort : defaultHttpPort),
        name: inputArg.name,
        manufacturer: inputArg.manufacturer || 'Jellyfin',
        model: inputArg.model || 'Jellyfin Server',
        metadata: {
          ...inputArg.metadata,
          url: inputArg.url,
          ssl,
        },
      },
    };
  }
}

export class JellyfinMdnsMatcher implements IDiscoveryMatcher<IJellyfinMdnsRecord> {
  public id = 'jellyfin-mdns-match';
  public source = 'mdns' as const;
  public description = 'Recognize Jellyfin-like mDNS HTTP advertisements when present.';

  public async matches(recordArg: IJellyfinMdnsRecord): Promise<IDiscoveryMatch> {
    const properties = { ...recordArg.txt, ...recordArg.properties };
    const haystack = `${recordArg.type || ''} ${recordArg.name || ''} ${recordArg.hostname || ''} ${Object.values(properties).join(' ')}`.toLowerCase();
    const matched = haystack.includes('jellyfin') || properties.jellyfin === 'true';
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'mDNS record does not advertise Jellyfin metadata.' };
    }
    const id = valueForKey(properties, 'id') || valueForKey(properties, 'serverid');
    return {
      matched: true,
      confidence: id ? 'certain' : 'medium',
      reason: 'mDNS record contains Jellyfin metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'mdns',
        integrationDomain: 'jellyfin',
        id,
        host: recordArg.host || recordArg.addresses?.[0],
        port: recordArg.port || defaultHttpPort,
        name: cleanName(recordArg.name),
        manufacturer: 'Jellyfin',
        model: 'Jellyfin Server',
        metadata: {
          mdnsType: recordArg.type,
          txt: properties,
        },
      },
    };
  }
}

export class JellyfinSsdpMatcher implements IDiscoveryMatcher<IJellyfinSsdpRecord> {
  public id = 'jellyfin-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize Jellyfin DLNA/UPnP SSDP advertisements.';

  public async matches(recordArg: IJellyfinSsdpRecord): Promise<IDiscoveryMatch> {
    const headers = recordArg.headers || {};
    const st = recordArg.st || headerValue(headers, 'st');
    const usn = recordArg.usn || headerValue(headers, 'usn');
    const location = recordArg.location || headerValue(headers, 'location');
    const server = recordArg.server || headerValue(headers, 'server');
    const model = headerValue(headers, 'modelName') || headerValue(headers, 'modelname') || headerValue(headers, 'model');
    const manufacturer = headerValue(headers, 'manufacturer');
    const haystack = `${st || ''} ${usn || ''} ${location || ''} ${server || ''} ${model || ''} ${manufacturer || ''}`.toLowerCase();
    const matched = haystack.includes('jellyfin');
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'SSDP record does not advertise Jellyfin metadata.' };
    }
    const parsedUrl = parseUrl(location);
    const id = usn?.replace(/^uuid:/i, '').split('::')[0];
    return {
      matched: true,
      confidence: id ? 'certain' : 'high',
      reason: 'SSDP record matches Jellyfin server metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'ssdp',
        integrationDomain: 'jellyfin',
        id,
        host: parsedUrl?.host,
        port: parsedUrl?.port || defaultHttpPort,
        manufacturer: manufacturer || 'Jellyfin',
        model: model || 'Jellyfin Server',
        metadata: {
          url: parsedUrl ? `${parsedUrl.ssl ? 'https' : 'http'}://${parsedUrl.host}:${parsedUrl.port || (parsedUrl.ssl ? defaultHttpsPort : defaultHttpPort)}` : location,
          ssdpSt: st,
          ssdpUsn: usn,
          server,
          ssl: parsedUrl?.ssl,
        },
      },
    };
  }
}

export class JellyfinCandidateValidator implements IDiscoveryValidator {
  public id = 'jellyfin-candidate-validator';
  public description = 'Validate Jellyfin server candidate metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const haystack = `${candidateArg.integrationDomain || ''} ${candidateArg.name || ''} ${candidateArg.manufacturer || ''} ${candidateArg.model || ''}`.toLowerCase();
    const matched = haystack.includes('jellyfin') || Boolean(candidateArg.metadata?.jellyfin);
    return {
      matched,
      confidence: matched && candidateArg.id ? 'certain' : matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Jellyfin metadata.' : 'Candidate is not Jellyfin.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id,
    };
  }
}

export const createJellyfinDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'jellyfin', displayName: 'Jellyfin' })
    .addMatcher(new JellyfinManualMatcher())
    .addMatcher(new JellyfinMdnsMatcher())
    .addMatcher(new JellyfinSsdpMatcher())
    .addValidator(new JellyfinCandidateValidator());
};

const parseUrl = (valueArg: string | undefined): { host: string; port?: number; ssl: boolean } | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    const url = new URL(valueArg);
    return {
      host: url.hostname,
      port: url.port ? Number(url.port) : undefined,
      ssl: url.protocol === 'https:',
    };
  } catch {
    return undefined;
  }
};

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

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

const cleanName = (valueArg: string | undefined): string | undefined => {
  return valueArg?.replace(/\._http\._tcp\.local\.?$/i, '').replace(/\.local\.?$/i, '').trim() || undefined;
};
