import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IDirectvManualEntry, IDirectvSsdpRecord } from './directv.types.js';
import { directvDefaultPort, directvDomain, directvSsdpDeviceType } from './directv.types.js';

export class DirectvSsdpMatcher implements IDiscoveryMatcher<IDirectvSsdpRecord> {
  public id = 'directv-ssdp-match';
  public source = 'ssdp' as const;
  public description = 'Recognize DirecTV receivers from UPnP MediaServer SSDP advertisements.';

  public async matches(recordArg: IDirectvSsdpRecord): 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 manufacturer = recordArg.manufacturer || recordArg.upnp?.manufacturer || headerValue(headers, 'manufacturer');
    const serial = recordArg.serialNumber || recordArg.upnp?.serialNumber || recordArg.upnp?.serial;
    const receiverId = stripReceiverPrefix(serial);
    const matchedDeviceType = deviceType === directvSsdpDeviceType || Boolean(usn?.includes(directvSsdpDeviceType));
    const matchedManufacturer = isDirectvText(manufacturer);

    if (!matchedDeviceType || !matchedManufacturer) {
      return { matched: false, confidence: 'low', reason: 'SSDP record is not a DirecTV MediaServer advertisement.' };
    }

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

export class DirectvManualMatcher implements IDiscoveryMatcher<IDirectvManualEntry> {
  public id = 'directv-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual DirecTV receiver setup entries.';

  public async matches(inputArg: IDirectvManualEntry): Promise<IDiscoveryMatch> {
    const endpoint = inputArg.location || inputArg.url;
    const parsedEndpoint = parseLocation(endpoint);
    const host = inputArg.host || parsedEndpoint?.hostname;
    const receiverId = stripReceiverPrefix(inputArg.receiverId || inputArg.id || stringMetadata(inputArg.metadata, 'receiverId'));
    const hinted = Boolean(inputArg.metadata?.directv || inputArg.metadata?.direcTV || isDirectvText(inputArg.manufacturer) || isDirectvText(inputArg.model) || isDirectvText(inputArg.name));
    if (!host && !hinted && !receiverId) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain DirecTV setup hints.' };
    }

    const id = receiverId || inputArg.id || host;
    return {
      matched: true,
      confidence: host && receiverId ? 'certain' : host ? 'high' : 'medium',
      reason: 'Manual entry can start DirecTV setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: directvDomain,
        id,
        host,
        port: inputArg.port || parsedEndpoint?.port || directvDefaultPort,
        name: inputArg.name || host,
        manufacturer: inputArg.manufacturer || 'DirecTV',
        model: inputArg.model,
        serialNumber: receiverId,
        metadata: { ...inputArg.metadata, receiverId, location: endpoint },
      },
    };
  }
}

export class DirectvCandidateValidator implements IDiscoveryValidator {
  public id = 'directv-candidate-validator';
  public description = 'Validate DirecTV discovery candidate metadata.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const text = [candidateArg.integrationDomain, candidateArg.manufacturer, candidateArg.model, candidateArg.name, metadata.manufacturer, metadata.deviceType].filter(Boolean).join(' ');
    const receiverId = stripReceiverPrefix(candidateArg.serialNumber || stringMetadata(metadata, 'receiverId'));
    const matched = candidateArg.integrationDomain === directvDomain
      || (metadata.deviceType === directvSsdpDeviceType && isDirectvText(text))
      || isDirectvText(text)
      || Boolean(receiverId || metadata.directv || metadata.direcTV);
    return {
      matched,
      confidence: matched && candidateArg.host && (candidateArg.id || receiverId) ? 'certain' : matched && candidateArg.host ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has DirecTV metadata.' : 'Candidate is not DirecTV.',
      candidate: matched ? candidateArg : undefined,
      normalizedDeviceId: candidateArg.id || receiverId,
    };
  }
}

export const createDirectvDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: directvDomain, displayName: 'DirecTV' })
    .addMatcher(new DirectvSsdpMatcher())
    .addMatcher(new DirectvManualMatcher())
    .addValidator(new DirectvCandidateValidator());
};

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;
  }
};

const stripReceiverPrefix = (valueArg?: string): string | undefined => {
  const value = valueArg?.replace(/^uuid:/i, '').replace(/^RID-/i, '').replace(/\s+/g, '').trim();
  return value || undefined;
};

const isDirectvText = (valueArg: unknown): boolean => {
  return typeof valueArg === 'string' && valueArg.toLowerCase().replace(/\s+/g, '').includes('directv');
};

const stringMetadata = (metadataArg: Record<string, unknown> | undefined, keyArg: string): string | undefined => {
  const value = metadataArg?.[keyArg];
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
};
