import { DiscoveryDescriptor } from '../../core/classes.discoverydescriptor.js';
import type { IDiscoveryCandidate, IDiscoveryContext, IDiscoveryMatch, IDiscoveryMatcher, IDiscoveryValidator } from '../../core/types.js';
import { SynologySrmMapper } from './synology_srm.mapper.js';
import type { ISynologySrmHttpDiscoveryRecord, ISynologySrmManualDiscoveryRecord, ISynologySrmSnapshot } from './synology_srm.types.js';
import { synologySrmDefaultSsl, synologySrmDomain } from './synology_srm.types.js';

const srmTextHints = ['synology', 'srm', 'router manager', 'rt2600', 'rt6600', 'mr2200', 'wrx560', 'network center'];

export class SynologySrmManualMatcher implements IDiscoveryMatcher<ISynologySrmManualDiscoveryRecord> {
  public id = 'synology-srm-manual-match';
  public source = 'manual' as const;
  public description = 'Recognize manual Synology SRM router setup entries and snapshot-only records.';

  public async matches(inputArg: ISynologySrmManualDiscoveryRecord, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const metadata = inputArg.metadata || {};
    const parsedUrl = parseUrl(inputArg.url);
    const snapshot = inputArg.snapshot || metadata.snapshot as ISynologySrmSnapshot | undefined;
    const host = inputArg.host || parsedUrl?.host || snapshot?.router.host;
    const text = textValue(inputArg.integrationDomain, inputArg.manufacturer, inputArg.model, inputArg.name, metadata.manufacturer, metadata.model, metadata.name, snapshot?.router.manufacturer, snapshot?.router.model, snapshot?.router.name);
    const matched = inputArg.integrationDomain === synologySrmDomain
      || metadata.synologySrm === true
      || Boolean(snapshot)
      || Boolean(host)
      || srmTextHints.some((hintArg) => text.includes(hintArg));

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'Manual entry does not contain Synology SRM setup hints.' };
    }

    const ssl = booleanValue(inputArg.ssl) ?? parsedUrl?.ssl ?? booleanValue(metadata.ssl) ?? snapshot?.router.ssl ?? synologySrmDefaultSsl;
    const port = inputArg.port || parsedUrl?.port || snapshot?.router.port || SynologySrmMapper.defaultPort(ssl);
    const mac = SynologySrmMapper.normalizeMac(inputArg.macAddress || snapshot?.router.macAddress || snapshot?.network?.macs?.[0]);
    const id = inputArg.id || snapshot?.router.serialNumber || mac || snapshot?.router.id || (host ? `${host}:${port}` : undefined);
    return {
      matched: true,
      confidence: snapshot || mac ? 'certain' : host ? 'high' : 'medium',
      reason: snapshot ? 'Manual entry includes a Synology SRM snapshot.' : 'Manual entry can start Synology SRM setup.',
      normalizedDeviceId: id,
      candidate: {
        source: 'manual',
        integrationDomain: synologySrmDomain,
        id,
        host,
        port,
        name: inputArg.name || snapshot?.router.name || snapshot?.router.hostname || host || 'Synology SRM',
        manufacturer: inputArg.manufacturer || 'Synology',
        model: inputArg.model || snapshot?.router.model || 'SRM Router',
        serialNumber: inputArg.serialNumber || snapshot?.router.serialNumber,
        macAddress: mac,
        metadata: {
          ...metadata,
          synologySrm: true,
          ssl,
          verifySsl: inputArg.verifySsl ?? metadata.verifySsl,
          username: inputArg.username ?? metadata.username,
          password: inputArg.password ?? metadata.password,
          snapshot,
          hasSnapshot: Boolean(snapshot),
          upstreamDeviceTrackerOnly: true,
          liveSrmApiImplemented: false,
        },
      },
      metadata: { ssl, hasSnapshot: Boolean(snapshot), upstreamDeviceTrackerOnly: true, liveSrmApiImplemented: false },
    };
  }
}

export class SynologySrmHttpMatcher implements IDiscoveryMatcher<ISynologySrmHttpDiscoveryRecord> {
  public id = 'synology-srm-http-match';
  public source = 'http' as const;
  public description = 'Recognize local HTTP candidates that look like SRM web/API endpoints.';

  public async matches(inputArg: ISynologySrmHttpDiscoveryRecord, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const metadata = inputArg.metadata || {};
    const url = inputArg.url || inputArg.location;
    const parsedUrl = parseUrl(url);
    const headers = normalizeKeys(inputArg.headers || {});
    const host = inputArg.host || parsedUrl?.host;
    const port = inputArg.port || parsedUrl?.port;
    const text = textValue(url, inputArg.name, inputArg.manufacturer, inputArg.model, headers.server, headers['x-powered-by'], metadata.name, metadata.manufacturer, metadata.model);
    const matched = metadata.synologySrm === true
      || port === 8000
      || port === 8001
      || Boolean(parsedUrl?.path?.includes('/webapi/') || parsedUrl?.path?.includes('/webman/'))
      || srmTextHints.some((hintArg) => text.includes(hintArg));

    if (!matched) {
      return { matched: false, confidence: 'low', reason: 'HTTP candidate does not look like a Synology SRM endpoint.' };
    }

    const ssl = inputArg.ssl ?? parsedUrl?.ssl ?? booleanValue(metadata.ssl) ?? (port === 8000 ? false : synologySrmDefaultSsl);
    const resolvedPort = port || SynologySrmMapper.defaultPort(ssl);
    const id = host ? `${host}:${resolvedPort}` : undefined;
    return {
      matched: true,
      confidence: host && (port === 8000 || port === 8001 || parsedUrl?.path?.includes('/webapi/')) ? 'high' : host ? 'medium' : 'low',
      reason: 'HTTP candidate has Synology SRM endpoint hints.',
      normalizedDeviceId: id,
      candidate: {
        source: 'http',
        integrationDomain: synologySrmDomain,
        id,
        host,
        port: resolvedPort,
        name: inputArg.name || 'Synology SRM',
        manufacturer: inputArg.manufacturer || 'Synology',
        model: inputArg.model || 'SRM Router',
        metadata: {
          ...metadata,
          synologySrm: true,
          ssl,
          url,
          headers,
          upstreamDeviceTrackerOnly: true,
          liveSrmApiImplemented: false,
        },
      },
      metadata: { ssl, upstreamDeviceTrackerOnly: true, liveSrmApiImplemented: false },
    };
  }
}

export class SynologySrmCandidateValidator implements IDiscoveryValidator {
  public id = 'synology-srm-candidate-validator';
  public description = 'Validate Synology SRM candidates have a local host or snapshot plus router metadata.';

  public async validate(candidateArg: IDiscoveryCandidate, contextArg: IDiscoveryContext): Promise<IDiscoveryMatch> {
    void contextArg;
    const metadata = candidateArg.metadata || {};
    const snapshot = metadata.snapshot as ISynologySrmSnapshot | undefined;
    const text = textValue(candidateArg.integrationDomain, candidateArg.manufacturer, candidateArg.model, candidateArg.name, metadata.manufacturer, metadata.model, metadata.name);
    const matched = candidateArg.integrationDomain === synologySrmDomain
      || metadata.synologySrm === true
      || Boolean(snapshot)
      || candidateArg.port === 8000
      || candidateArg.port === 8001
      || srmTextHints.some((hintArg) => text.includes(hintArg));
    const hasSource = Boolean(candidateArg.host || snapshot);

    if (!matched || !hasSource) {
      return {
        matched: false,
        confidence: matched ? 'medium' : 'low',
        reason: matched ? 'Synology SRM candidate lacks host or snapshot information.' : 'Candidate is not Synology SRM.',
      };
    }

    const ssl = booleanValue(metadata.ssl) ?? snapshot?.router.ssl ?? synologySrmDefaultSsl;
    const port = candidateArg.port || snapshot?.router.port || SynologySrmMapper.defaultPort(ssl);
    const mac = SynologySrmMapper.normalizeMac(candidateArg.macAddress || snapshot?.router.macAddress || snapshot?.network?.macs?.[0]);
    const normalizedDeviceId = candidateArg.id || snapshot?.router.serialNumber || mac || (candidateArg.host ? `${candidateArg.host}:${port}` : snapshot?.router.id);
    return {
      matched: true,
      confidence: snapshot || candidateArg.serialNumber || mac ? 'certain' : candidateArg.host ? 'high' : 'medium',
      reason: 'Candidate has Synology SRM metadata and a usable local source.',
      normalizedDeviceId,
      candidate: {
        ...candidateArg,
        id: candidateArg.id || normalizedDeviceId,
        integrationDomain: synologySrmDomain,
        port,
        macAddress: mac || candidateArg.macAddress,
        metadata: {
          ...metadata,
          synologySrm: true,
          ssl,
          upstreamDeviceTrackerOnly: true,
          liveSrmApiImplemented: false,
        },
      },
      metadata: { ssl, upstreamDeviceTrackerOnly: true, liveSrmApiImplemented: false },
    };
  }
}

export const createSynologySrmDiscoveryDescriptor = (): DiscoveryDescriptor => {
  return new DiscoveryDescriptor({ integrationDomain: synologySrmDomain, displayName: 'Synology SRM' })
    .addMatcher(new SynologySrmManualMatcher())
    .addMatcher(new SynologySrmHttpMatcher())
    .addValidator(new SynologySrmCandidateValidator());
};

const parseUrl = (valueArg: string | undefined): { host: string; port?: number; ssl: boolean; path: string } | undefined => {
  if (!valueArg) {
    return undefined;
  }
  try {
    const url = new URL(valueArg);
    return {
      host: url.hostname,
      port: url.port ? Number(url.port) : undefined,
      ssl: url.protocol === 'https:',
      path: url.pathname,
    };
  } catch {
    return undefined;
  }
};

const normalizeKeys = (recordArg: Record<string, string | undefined>): Record<string, string | undefined> => {
  const normalized: Record<string, string | undefined> = {};
  for (const [key, value] of Object.entries(recordArg)) {
    normalized[key.toLowerCase()] = value;
  }
  return normalized;
};

const stringValue = (valueArg: unknown): string | undefined => typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;

const booleanValue = (valueArg: unknown): boolean | undefined => {
  if (typeof valueArg === 'boolean') {
    return valueArg;
  }
  if (typeof valueArg === 'string') {
    if (['true', '1', 'yes', 'on'].includes(valueArg.toLowerCase())) {
      return true;
    }
    if (['false', '0', 'no', 'off'].includes(valueArg.toLowerCase())) {
      return false;
    }
  }
  return undefined;
};

const textValue = (...valuesArg: unknown[]): string => valuesArg.map((valueArg) => stringValue(valueArg)).filter((valueArg): valueArg is string => Boolean(valueArg)).join(' ').toLowerCase();
