import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IDlnaDmsManualEntry, IDlnaDmsSsdpRecord, IDlnaDmsSsdpService } from './dlna_dms.types.js';
import { dlnaDmsDefaultPort, dlnaDmsDomain } from './dlna_dms.types.js';

const dlnaDmsDeviceTypes = [
  'urn:schemas-upnp-org:device:MediaServer:1',
  'urn:schemas-upnp-org:device:MediaServer:2',
  'urn:schemas-upnp-org:device:MediaServer:3',
  'urn:schemas-upnp-org:device:MediaServer:4',
];

const requiredServiceIds = new Set([
  'urn:upnp-org:serviceId:ContentDirectory',
  'urn:upnp-org:serviceId:ConnectionManager',
]);

export class DlnaDmsSsdpMatcher implements IDiscoveryMatcher<IDlnaDmsSsdpRecord> {
  public id = 'dlna-dms-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize UPnP/DLNA MediaServer SSDP advertisements.';

  public async matches(recordArg: IDlnaDmsSsdpRecord): Promise<IDiscoveryMatch> {
    const headers = recordArg.headers || {};
    const st = recordArg.st || headerValue(headers, 'st');
    const nt = recordArg.nt || headerValue(headers, 'nt');
    const usn = recordArg.usn || headerValue(headers, 'usn');
    const location = recordArg.location || headerValue(headers, 'location');
    const deviceType = recordArg.deviceType || recordArg.upnp?.deviceType || st || nt;
    const matchedDeviceType = typeof deviceType === 'string' && dlnaDmsDeviceTypes.includes(deviceType);
    const matchedUsn = typeof usn === 'string' && dlnaDmsDeviceTypes.some((typeArg) => usn.includes(typeArg));

    if (!matchedDeviceType && !matchedUsn) {
      return { matched: false, confidence: 'low', reason: 'SSDP record is not a DLNA MediaServer.' };
    }

    const serviceIds = this.serviceIds(recordArg);
    const hasRequiredServices = serviceIds.length === 0 || [...requiredServiceIds].every((serviceIdArg) => serviceIds.includes(serviceIdArg));
    if (!hasRequiredServices) {
      return { matched: false, confidence: 'medium', reason: 'SSDP record lacks required DMS services.' };
    }

    const parsedLocation = parseLocation(location);
    const udn = recordArg.udn || recordArg.ssdpUdn || usn?.split('::')[0];
    const id = usn || udn;
    return {
      matched: true,
      confidence: id && location ? 'certain' : location ? 'high' : 'medium',
      reason: 'SSDP record matches DLNA MediaServer metadata.',
      normalizedDeviceId: id,
      candidate: {
        source: 'ssdp',
        integrationDomain: dlnaDmsDomain,
        id,
        host: parsedLocation?.hostname,
        port: parsedLocation?.port,
        name: recordArg.upnp?.friendlyName,
        manufacturer: recordArg.upnp?.manufacturer,
        model: recordArg.upnp?.modelName,
        metadata: { st, nt, usn, udn, location, deviceType, serviceIds },
      },
    };
  }

  private serviceIds(recordArg: IDlnaDmsSsdpRecord): string[] {
    const service = recordArg.upnp?.serviceList?.service;
    const services: IDlnaDmsSsdpService[] = Array.isArray(service) ? service : service ? [service] : [];
    return services.map((serviceArg) => serviceArg.serviceId).filter((serviceIdArg): serviceIdArg is string => Boolean(serviceIdArg));
  }
}

export class DlnaDmsManualMatcher implements IDiscoveryMatcher<IDlnaDmsManualEntry> {
  public id = 'dlna-dms-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual DLNA DMS setup entries.';

  public async matches(inputArg: IDlnaDmsManualEntry): Promise<IDiscoveryMatch> {
    const location = inputArg.location || inputArg.url || this.locationFromHost(inputArg);
    const model = inputArg.model?.toLowerCase() || '';
    const manufacturer = inputArg.manufacturer?.toLowerCase() || '';
    const name = inputArg.name?.toLowerCase() || '';
    const hinted = Boolean(inputArg.metadata?.dlnaDms || inputArg.metadata?.dlna_dms || model.includes('media server') || manufacturer.includes('dlna') || name.includes('dlna'));
    const matched = Boolean(location || inputArg.host || hinted);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain DLNA DMS setup hints.' };
    }

    const parsedLocation = parseLocation(location);
    const id = inputArg.deviceId || inputArg.udn || inputArg.id || location;
    return {
      matched: true,
      confidence: location ? 'high' : 'medium',
      reason: 'Manual entry can start DLNA DMS setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: dlnaDmsDomain,
        id,
        host: parsedLocation?.hostname || inputArg.host,
        port: parsedLocation?.port || inputArg.port,
        name: inputArg.name,
        manufacturer: inputArg.manufacturer,
        model: inputArg.model,
        metadata: { ...inputArg.metadata, location },
      },
    };
  }

  private locationFromHost(inputArg: IDlnaDmsManualEntry): string | undefined {
    if (!inputArg.host) {
      return undefined;
    }
    const path = inputArg.path || '/description.xml';
    return `http://${inputArg.host}:${inputArg.port || dlnaDmsDefaultPort}${path.startsWith('/') ? path : `/${path}`}`;
  }
}

export class DlnaDmsCandidateValidator implements IDiscoveryValidator {
  public id = 'dlna-dms-candidate-validator';
  public description = 'Validate DLNA DMS candidate metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const deviceType = typeof candidateArg.metadata?.deviceType === 'string' ? candidateArg.metadata.deviceType : undefined;
    const haystack = `${candidateArg.integrationDomain || ''} ${candidateArg.name || ''} ${candidateArg.manufacturer || ''} ${candidateArg.model || ''}`.toLowerCase();
    const matched = candidateArg.integrationDomain === dlnaDmsDomain
      || Boolean(deviceType && dlnaDmsDeviceTypes.includes(deviceType))
      || haystack.includes('dlna')
      || haystack.includes('media server')
      || candidateArg.metadata?.dlnaDms === true
      || candidateArg.metadata?.dlna_dms === true;
    return {
      matched,
      confidence: matched && candidateArg.id && candidateArg.host ? 'certain' : matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has DLNA DMS metadata.' : 'Candidate is not a DLNA DMS server.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id,
    };
  }
}

export const createDlnaDmsDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: dlnaDmsDomain, displayName: 'DLNA Digital Media Server' })
    .addMatcher(new DlnaDmsSsdpMatcher())
    .addMatcher(new DlnaDmsManualMatcher())
    .addValidator(new DlnaDmsCandidateValidator());
};

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 parseLocation = (valueArg?: string): { hostname: string; port?: number } | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    const url = new URL(valueArg);
    return { hostname: url.hostname, port: url.port ? Number(url.port) : undefined };
  } catch {
    return undefined;
  }
};
