import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IOpenthermGwManualDiscoveryInput } from './opentherm_gw.types.js';
import { openthermGwDefaultTcpPort, openthermGwDomain } from './opentherm_gw.types.js';

export class OpenthermGwManualMatcher implements IDiscoveryMatcher<IOpenthermGwManualDiscoveryInput> {
  public id = 'opentherm-gw-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual OpenTherm Gateway raw TCP or serial bridge entries.';

  public async matches(inputArg: IOpenthermGwManualDiscoveryInput): Promise<IDiscoveryMatch> {
    const text = `${inputArg.name || ''} ${inputArg.manufacturer || ''} ${inputArg.model || ''}`.toLowerCase();
    const hasMetadata = Boolean(inputArg.metadata?.opentherm_gw || inputArg.metadata?.openthermGw || inputArg.metadata?.otgw);
    const matched = Boolean(inputArg.host || inputArg.device || hasMetadata || text.includes('opentherm'));
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain OpenTherm Gateway hints.' };
    }

    const port = inputArg.host ? inputArg.port || openthermGwDefaultTcpPort : inputArg.port;
    const id = inputArg.id || (inputArg.host ? `${inputArg.host}:${port}` : inputArg.device);
    return {
      matched: true,
      confidence: inputArg.host || inputArg.device ? 'high' : 'medium',
      reason: 'Manual entry can start OpenTherm Gateway setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: openthermGwDomain,
        id,
        host: inputArg.host,
        port,
        name: inputArg.name || 'OpenTherm Gateway',
        manufacturer: inputArg.manufacturer || 'Schelte Bron',
        model: inputArg.model || 'OpenTherm Gateway',
        metadata: {
          ...inputArg.metadata,
          device: inputArg.device,
          rawTcp: Boolean(inputArg.host),
        },
      },
    };
  }
}

export class OpenthermGwCandidateValidator implements IDiscoveryValidator {
  public id = 'opentherm-gw-candidate-validator';
  public description = 'Validate OpenTherm Gateway manual candidates.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const text = `${candidateArg.name || ''} ${candidateArg.manufacturer || ''} ${candidateArg.model || ''}`.toLowerCase();
    const matched = candidateArg.integrationDomain === openthermGwDomain
      || text.includes('opentherm')
      || Boolean(candidateArg.metadata?.opentherm_gw || candidateArg.metadata?.openthermGw || candidateArg.metadata?.otgw);
    const hasEndpoint = Boolean(candidateArg.host || candidateArg.metadata?.device || candidateArg.metadata?.snapshot);

    if (!matched || !hasEndpoint) {
      return {
        matched: false,
        confidence: matched ? 'medium' : 'low',
        reason: matched ? 'OpenTherm Gateway candidate lacks host, serial device, or snapshot metadata.' : 'Candidate is not an OpenTherm Gateway.',
      };
    }

    const port = candidateArg.host ? candidateArg.port || openthermGwDefaultTcpPort : candidateArg.port;
    return {
      matched: true,
      confidence: candidateArg.id ? 'certain' : 'high',
      reason: 'Candidate has OpenTherm Gateway metadata and a usable endpoint.',
      normalizedDeviceId: candidateArg.id || (candidateArg.host ? `${candidateArg.host}:${port}` : String(candidateArg.metadata?.device || 'opentherm_gateway')),
      candidate: {
        ...candidateArg,
        port,
      },
    };
  }
}

export const createOpenthermGwDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: openthermGwDomain, displayName: 'OpenTherm Gateway' })
    .addMatcher(new OpenthermGwManualMatcher())
    .addValidator(new OpenthermGwCandidateValidator());
};
