import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IChannelsFavoriteChannel, IChannelsHttpApiEntry, IChannelsManualEntry, IChannelsSnapshot, IChannelsStatus } from './channels.types.js';
import { channelsDefaultPort } from './channels.types.js';

const channelsDomain = 'channels';

export class ChannelsManualMatcher implements IDiscoveryMatcher<IChannelsManualEntry> {
  public id = 'channels-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Channels app HTTP API endpoints.';

  public async matches(inputArg: IChannelsManualEntry, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const endpoint = endpointFromInput(inputArg);
    const metadata = inputArg.metadata || {};
    const hasChannelsHint = Boolean(metadata.channels || metadata.channelsApi || metadata.channelsDvr || metadata.channels_dvr || includesChannels(inputArg.name) || includesChannels(inputArg.model) || includesChannels(inputArg.manufacturer));
    const matched = Boolean(endpoint.host || inputArg.snapshot || inputArg.status || hasChannelsHint);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain a Channels host or Channels metadata.' };
    }

    const normalizedDeviceId = inputArg.id || endpoint.host || inputArg.name;
    return {
      matched: true,
      confidence: endpoint.host ? 'high' : hasChannelsHint ? 'medium' : 'low',
      reason: endpoint.host ? 'Manual entry contains a Channels HTTP API host.' : 'Manual entry contains Channels snapshot metadata.',
      normalizedDeviceId,
      candidate: {
        source: 'manual',
        integrationDomain: channelsDomain,
        id: normalizedDeviceId,
        host: endpoint.host,
        port: endpoint.port || channelsDefaultPort,
        name: inputArg.name || endpoint.host || 'Channels',
        manufacturer: inputArg.manufacturer || 'Fancy Bits',
        model: inputArg.model || 'Channels',
        metadata: {
          ...metadata,
          url: endpoint.url,
          status: inputArg.status,
          favoriteChannels: inputArg.favoriteChannels,
          snapshot: inputArg.snapshot,
          discoveryProtocol: 'manual',
          channelsApi: true,
        },
      },
    };
  }
}

export class ChannelsHttpApiMatcher implements IDiscoveryMatcher<IChannelsHttpApiEntry> {
  public id = 'channels-http-api-match';
  public source = 'http' as const;
  public description = 'Recognize Channels HTTP API status responses.';

  public async matches(inputArg: IChannelsHttpApiEntry, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const endpoint = endpointFromInput(inputArg);
    const status = statusFromHttpEntry(inputArg);
    const matched = Boolean(status || inputArg.metadata?.channelsApi);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'HTTP payload is not a Channels API status response.' };
    }
    const normalizedDeviceId = inputArg.id || endpoint.host || inputArg.name;
    return {
      matched: true,
      confidence: status && endpoint.host ? 'high' : 'medium',
      reason: 'HTTP payload matches Channels API status shape.',
      normalizedDeviceId,
      candidate: {
        source: 'http',
        integrationDomain: channelsDomain,
        id: normalizedDeviceId,
        host: endpoint.host,
        port: endpoint.port || channelsDefaultPort,
        name: inputArg.name || endpoint.host || 'Channels',
        manufacturer: 'Fancy Bits',
        model: 'Channels',
        metadata: {
          ...inputArg.metadata,
          url: endpoint.url,
          status,
          favoriteChannels: inputArg.favoriteChannels,
          channelsApi: true,
        },
      },
    };
  }
}

export class ChannelsCandidateValidator implements IDiscoveryValidator {
  public id = 'channels-candidate-validator';
  public description = 'Validate Channels HTTP API candidates.';

  public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const metadata = candidateArg.metadata || {};
    const matched = candidateArg.integrationDomain === channelsDomain
      || Boolean(metadata.channels || metadata.channelsApi || metadata.channelsDvr || metadata.channels_dvr)
      || (candidateArg.source === 'manual' && Boolean(candidateArg.host) && includesChannels(`${candidateArg.name || ''} ${candidateArg.manufacturer || ''} ${candidateArg.model || ''}`));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Candidate is not Channels.' };
    }
    return {
      matched: true,
      confidence: candidateArg.host ? 'high' : 'medium',
      reason: 'Candidate has Channels HTTP API metadata.',
      normalizedDeviceId: candidateArg.id || candidateArg.host,
      candidate: {
        ...candidateArg,
        integrationDomain: channelsDomain,
        port: candidateArg.port || channelsDefaultPort,
        manufacturer: candidateArg.manufacturer || 'Fancy Bits',
        model: candidateArg.model || 'Channels',
        metadata: {
          ...metadata,
          channelsApi: true,
        },
      },
    };
  }
}

export const createChannelsDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: channelsDomain, displayName: 'Channels' })
    .addMatcher(new ChannelsManualMatcher())
    .addMatcher(new ChannelsHttpApiMatcher())
    .addValidator(new ChannelsCandidateValidator());
};

const statusFromHttpEntry = (inputArg: IChannelsHttpApiEntry): IChannelsStatus | undefined => {
  const status = inputArg.status || inputArg.response;
  if (!status || typeof status !== 'object') {
    return undefined;
  }
  if (typeof status.status === 'string' || status.channel || status.now_playing || status.nowPlaying) {
    return status;
  }
  return undefined;
};

const endpointFromInput = (inputArg: { host?: string; port?: number; url?: string }): { host?: string; port?: number; url?: string } => {
  const raw = inputArg.url || inputArg.host;
  if (raw) {
    const value = /^https?:\/\//i.test(raw) ? raw : `http://${raw}`;
    try {
      const url = new URL(value);
      if (!url.port) {
        url.port = String(inputArg.port || channelsDefaultPort);
      }
      url.pathname = '';
      url.search = '';
      url.hash = '';
      return {
        host: url.hostname,
        port: Number(url.port) || channelsDefaultPort,
        url: url.toString().replace(/\/$/, ''),
      };
    } catch {
      return {
        host: inputArg.host,
        port: inputArg.port || channelsDefaultPort,
      };
    }
  }
  return { port: inputArg.port || channelsDefaultPort };
};

const includesChannels = (valueArg: string | undefined): boolean => {
  return Boolean(valueArg?.toLowerCase().includes('channels'));
};

export type TChannelsDiscoveryStatus = IChannelsStatus;
export type TChannelsDiscoverySnapshot = IChannelsSnapshot;
export type TChannelsDiscoveryFavoriteChannel = IChannelsFavoriteChannel;
