import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IDlnaDmrManualEntry, IDlnaDmrSsdpRecord, IDlnaDmrSsdpService } from './dlna_dmr.types.js';

const dlnaDmrDeviceTypes = [
  'urn:schemas-upnp-org:device:MediaRenderer:1',
  'urn:schemas-upnp-org:device:MediaRenderer:2',
  'urn:schemas-upnp-org:device:MediaRenderer:3',
];

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

export class DlnaDmrSsdpMatcher implements IDiscoveryMatcher<IDlnaDmrSsdpRecord> {
  public id = 'dlna-dmr-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize UPnP/DLNA MediaRenderer SSDP advertisements.';

  public async matches(recordArg: IDlnaDmrSsdpRecord): Promise<IDiscoveryMatch> {
    const headers = recordArg.headers || {};
    const st = recordArg.st || headers.st || headers.ST;
    const nt = recordArg.nt || headers.nt || headers.NT;
    const usn = recordArg.usn || headers.usn || headers.USN;
    const location = recordArg.location || headers.location || headers.LOCATION;
    const deviceType = recordArg.deviceType || recordArg.upnp?.deviceType || st || nt;
    const matchedDeviceType = typeof deviceType === 'string' && dlnaDmrDeviceTypes.includes(deviceType);
    const matchedUsn = typeof usn === 'string' && dlnaDmrDeviceTypes.some((typeArg) => usn.includes(typeArg));

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

    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 DMR services.' };
    }

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

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

export class DlnaDmrManualMatcher implements IDiscoveryMatcher<IDlnaDmrManualEntry> {
  public id = 'dlna-dmr-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual DLNA DMR setup entries.';

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

    const url = location ? new URL(location) : undefined;
    const id = inputArg.udn || inputArg.id;
    return {
      matched: true,
      confidence: location ? 'high' : 'medium',
      reason: 'Manual entry can start DLNA DMR setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: 'dlna_dmr',
        id,
        host: url?.hostname || inputArg.host,
        port: url?.port ? Number(url.port) : inputArg.port,
        name: inputArg.name,
        manufacturer: inputArg.manufacturer,
        model: inputArg.model,
        metadata: { ...inputArg.metadata, location },
      },
    };
  }

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

export class DlnaDmrCandidateValidator implements IDiscoveryValidator {
  public id = 'dlna-dmr-candidate-validator';
  public description = 'Validate DLNA DMR candidate metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const deviceType = typeof candidateArg.metadata?.deviceType === 'string' ? candidateArg.metadata.deviceType : undefined;
    const matched = candidateArg.integrationDomain === 'dlna_dmr' || Boolean(deviceType && dlnaDmrDeviceTypes.includes(deviceType));
    return {
      matched,
      confidence: matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has DLNA DMR metadata.' : 'Candidate is not a DLNA DMR renderer.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id,
    };
  }
}

export const createDlnaDmrDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'dlna_dmr', displayName: 'DLNA Digital Media Renderer' })
    .addMatcher(new DlnaDmrSsdpMatcher())
    .addMatcher(new DlnaDmrManualMatcher())
    .addValidator(new DlnaDmrCandidateValidator());
};
