import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import type { IVelbusManualDiscoveryEntry, IVelbusUsbDiscoveryEntry, TVelbusGatewayConnection } from './velbus.types.js';

const velbusUsbVid = '10cf';
const velbusUsbPids = new Set(['0b1b', '0516', '0517', '0518']);

export class VelbusManualMatcher implements IDiscoveryMatcher<IVelbusManualDiscoveryEntry> {
  public id = 'velbus-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Velbus serial and TCP/IP gateway setup entries.';

  public async matches(inputArg: IVelbusManualDiscoveryEntry): Promise<IDiscoveryMatch> {
    const connection = connectionFromManualEntry(inputArg);
    const haystack = `${inputArg.name || ''} ${inputArg.model || ''} ${inputArg.manufacturer || ''}`.toLowerCase();
    const hasVelbusHint = haystack.includes('velbus') || haystack.includes('velleman') || Boolean(inputArg.metadata?.velbus);
    const matched = connection === 'manual' ? false : hasVelbusHint || inputArg.connection === connection;
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Velbus serial or TCP/IP hints.' };
    }

    const tcpDsn = parseTcpDsn(inputArg.dsn);
    const serialPath = inputArg.serialPath || inputArg.device || inputArg.path || (connection === 'serial' ? inputArg.dsn : undefined);
    const tls = inputArg.tls ?? tcpDsn?.tls ?? (inputArg.dsn?.startsWith('tls://') || undefined);
    const host = inputArg.host || tcpDsn?.host;
    const port = inputArg.port || tcpDsn?.port || (connection === 'tcp' ? (tls === false ? 6000 : 27015) : undefined);
    const password = inputArg.password || tcpDsn?.password;
    const dsn = inputArg.dsn || (connection === 'serial' ? serialPath : dsnFromTcp(host, port, tls, password));
    const normalizedDeviceId = inputArg.serialNumber || inputArg.id || (connection === 'serial' ? `serial:${serialPath}` : `tcp:${host}:${port}`);

    return {
      matched: true,
      confidence: connection === 'tcp' && inputArg.host ? 'high' : serialPath ? 'high' : 'medium',
      reason: `Manual Velbus ${connection} entry can start configuration.`,
      normalizedDeviceId,
      candidate: {
        source: 'manual',
        integrationDomain: 'velbus',
        id: normalizedDeviceId,
        host,
        port,
        name: inputArg.name || (connection === 'tcp' ? 'Velbus TCP/IP Gateway' : 'Velbus Serial Gateway'),
        manufacturer: inputArg.manufacturer || 'Velleman',
        model: inputArg.model,
        serialNumber: inputArg.serialNumber,
        metadata: {
          ...inputArg.metadata,
          connection,
          dsn,
          serialPath,
          tls,
          password,
          vlpFile: inputArg.vlpFile,
        },
      },
    };
  }
}

export class VelbusUsbMatcher implements IDiscoveryMatcher<IVelbusUsbDiscoveryEntry> {
  public id = 'velbus-usb-match';
  public source = 'usb' as const;
  public description = 'Recognize Velbus USB interfaces from Home Assistant manifest VID/PID pairs.';

  public async matches(inputArg: IVelbusUsbDiscoveryEntry): Promise<IDiscoveryMatch> {
    const vid = normalizeUsbId(inputArg.vid);
    const pid = normalizeUsbId(inputArg.pid);
    const matched = vid === velbusUsbVid && velbusUsbPids.has(pid);
    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'USB device VID/PID is not a Velbus interface from the Home Assistant manifest.' };
    }
    const serialPath = inputArg.device;
    const normalizedDeviceId = inputArg.serialNumber || (serialPath ? `serial:${serialPath}` : `usb:${vid}:${pid}`);
    return {
      matched: true,
      confidence: inputArg.serialNumber || serialPath ? 'certain' : 'high',
      reason: 'USB VID/PID matches a Velbus interface from the Home Assistant manifest.',
      normalizedDeviceId,
      candidate: {
        source: 'usb',
        integrationDomain: 'velbus',
        id: normalizedDeviceId,
        name: inputArg.product || 'Velbus USB Gateway',
        manufacturer: inputArg.manufacturer || 'Velleman',
        model: inputArg.product,
        serialNumber: inputArg.serialNumber,
        metadata: {
          ...inputArg.metadata,
          connection: 'serial',
          dsn: serialPath,
          serialPath,
          vid,
          pid,
        },
      },
    };
  }
}

export class VelbusCandidateValidator implements IDiscoveryValidator {
  public id = 'velbus-candidate-validator';
  public description = 'Validate that a discovery candidate can configure a Velbus gateway.';

  public async validate(candidateArg: IDiscoveryCandidate): Promise<IDiscoveryMatch> {
    const metadata = candidateArg.metadata || {};
    const connection = metadata.connection === 'serial' || metadata.connection === 'tcp' ? metadata.connection : candidateArg.host ? 'tcp' : metadata.serialPath ? 'serial' : undefined;
    const haystack = `${candidateArg.name || ''} ${candidateArg.model || ''} ${candidateArg.manufacturer || ''}`.toLowerCase();
    const matched = candidateArg.integrationDomain === 'velbus' || haystack.includes('velbus') || haystack.includes('velleman') || Boolean(metadata.velbus) || Boolean(connection);
    return {
      matched,
      confidence: matched && (candidateArg.host || metadata.serialPath || metadata.dsn) ? 'high' : matched ? 'medium' : 'low',
      reason: matched ? 'Candidate has Velbus gateway metadata.' : 'Candidate is not Velbus.',
      normalizedDeviceId: candidateArg.id || candidateArg.serialNumber || (connection === 'tcp' ? `tcp:${candidateArg.host}:${candidateArg.port || 27015}` : metadata.serialPath ? `serial:${metadata.serialPath}` : undefined),
      candidate: matched ? {
        ...candidateArg,
        integrationDomain: 'velbus',
        manufacturer: candidateArg.manufacturer || 'Velleman',
        metadata: {
          ...metadata,
          connection,
        },
      } : undefined,
    };
  }
}

export const createVelbusDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: 'velbus', displayName: 'Velbus' })
    .addMatcher(new VelbusManualMatcher())
    .addMatcher(new VelbusUsbMatcher())
    .addValidator(new VelbusCandidateValidator());
};

const connectionFromManualEntry = (entryArg: IVelbusManualDiscoveryEntry): TVelbusGatewayConnection => {
  if (entryArg.connection === 'serial' || entryArg.connection === 'tcp') {
    return entryArg.connection;
  }
  if (entryArg.host) {
    return 'tcp';
  }
  if (parseTcpDsn(entryArg.dsn)) {
    return 'tcp';
  }
  if (entryArg.serialPath || entryArg.device || entryArg.path || entryArg.dsn?.startsWith('/')) {
    return 'serial';
  }
  return 'manual';
};

const dsnFromTcp = (hostArg: string | undefined, portArg: number | undefined, tlsArg: boolean | undefined, passwordArg: string | undefined): string | undefined => {
  if (!hostArg || !portArg) {
    return undefined;
  }
  const credentials = passwordArg ? `${passwordArg}@` : '';
  return `${tlsArg === false ? '' : 'tls://'}${credentials}${hostArg}:${portArg}`;
};

const parseTcpDsn = (dsnArg: string | undefined): { host: string; port: number; tls: boolean; password?: string } | undefined => {
  if (!dsnArg || dsnArg.startsWith('/')) {
    return undefined;
  }
  const tls = dsnArg.startsWith('tls://');
  const withoutScheme = dsnArg.replace(/^tls:\/\//, '');
  const [password, hostPort] = withoutScheme.includes('@') ? withoutScheme.split('@', 2) : [undefined, withoutScheme];
  const [host, portText] = hostPort.split(':');
  const port = Number(portText);
  return host && Number.isFinite(port) ? { host, port, tls, password } : undefined;
};

const normalizeUsbId = (valueArg: string | undefined): string => (valueArg || '').replace(/^0x/i, '').toLowerCase().padStart(4, '0');
