/**
 * Match an analyzer_analyses row to candidate IT Glue assets to audit.
 *
 * Inputs from the analysis fingerprint:
 *   - applications_involved → match against itg_flexible_assets.name (Application type)
 *   - device_classes / vendors_involved + ticket-mentioned device names →
 *     match against itg_configurations.name + hostname
 *
 * Same-client only (looked up via companies → itg_organizations name match).
 * Score: exact match (3) > word-boundary match (2) > substring (1).
 */

import postgresClient from '@/lib/services/postgres-client';

const APPLICATION_TYPE_ID = 3790;
const PER_KIND_LIMIT = 5;

export interface MatchedFlexibleAsset {
  id: string;
  name: string | null;
  type_name: string | null;
  score: number;
  matched_term: string;
}

export interface MatchedConfiguration {
  id: string;
  name: string;
  hostname: string | null;
  type_name: string | null;
  score: number;
  matched_term: string;
}

export interface AssetMatchResult {
  ticketNumber: string;
  organizationId: string | null;
  organizationName: string | null;
  flexibleAssets: MatchedFlexibleAsset[];
  configurations: MatchedConfiguration[];
}

interface AnalysisRow {
  ticket_number: string;
  fingerprint: Record<string, unknown> | null;
  company_id: string | null;
  itglue_org_id: string | null;
  itglue_org_name: string | null;
}

function normalizeTerm(s: string): string {
  return s.toLowerCase().trim();
}

function scoreMatch(haystack: string | null, needle: string): number {
  if (!haystack) return 0;
  const h = haystack.toLowerCase();
  const n = needle.toLowerCase();
  if (h === n) return 3;
  // Word boundary: surrounded by non-alphanum or start/end.
  const re = new RegExp(`(^|[^a-z0-9])${n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}([^a-z0-9]|$)`);
  if (re.test(h)) return 2;
  if (h.includes(n)) return 1;
  return 0;
}

function dedupeByKey<T extends { id: string }>(items: T[]): T[] {
  const seen = new Map<string, T>();
  for (const item of items) {
    const existing = seen.get(item.id);
    if (
      !existing ||
      ('score' in item &&
        'score' in existing &&
        (item as unknown as { score: number }).score >
          (existing as unknown as { score: number }).score)
    ) {
      seen.set(item.id, item);
    }
  }
  return Array.from(seen.values());
}

/**
 * Pull the analysis + ticket + IT Glue org mapping in one round-trip.
 * Returns null if the analysis isn't complete or the company can't be
 * mapped to an IT Glue organization.
 */
async function loadAnalysisContext(analysisId: string): Promise<AnalysisRow | null> {
  const res = await postgresClient.query<AnalysisRow>(
    `SELECT aa.ticket_number,
            aa.aggregate_fingerprint AS fingerprint,
            t.company_id::text       AS company_id,
            o.id::text               AS itglue_org_id,
            o.name                   AS itglue_org_name
       FROM analyzer_analyses aa
       JOIN tickets t ON t.ticket_number = aa.ticket_number
       LEFT JOIN companies c ON c.id = t.company_id
       LEFT JOIN itg_organizations o ON LOWER(o.name) = LOWER(c.company_name)
      WHERE aa.id = $1
        AND aa.status = 'complete'
      LIMIT 1`,
    [analysisId]
  );
  if (res.rowCount === 0) return null;
  return res.rows[0];
}

interface FlexAssetCandidate {
  id: string;
  name: string | null;
  type_name: string | null;
}

interface ConfigCandidate {
  id: string;
  name: string;
  hostname: string | null;
  type_name: string | null;
}

async function loadFlexAssetCandidates(orgId: string): Promise<FlexAssetCandidate[]> {
  const res = await postgresClient.query<FlexAssetCandidate>(
    `SELECT id::text                    AS id,
            name,
            flexible_asset_type_name    AS type_name
       FROM itg_flexible_assets
      WHERE organization_id = $1
        AND flexible_asset_type_id = $2
        AND COALESCE(archived, false) = false`,
    [orgId, APPLICATION_TYPE_ID]
  );
  return res.rows;
}

async function loadConfigurationCandidates(
  orgId: string
): Promise<ConfigCandidate[]> {
  const res = await postgresClient.query<ConfigCandidate>(
    `SELECT id::text                    AS id,
            name, hostname,
            configuration_type_name     AS type_name
       FROM itg_configurations
      WHERE organization_id = $1`,
    [orgId]
  );
  return res.rows;
}

function fingerprintTerms(fp: Record<string, unknown> | null): string[] {
  if (!fp) return [];
  const out = new Set<string>();
  for (const key of [
    'applications_involved',
    'device_classes',
    'vendors_involved',
  ]) {
    const arr = fp[key];
    if (Array.isArray(arr)) {
      for (const v of arr) {
        if (typeof v === 'string' && v.trim().length > 0) {
          out.add(normalizeTerm(v));
        }
      }
    }
  }
  return Array.from(out);
}

export async function matchAssetsForAnalysis(
  analysisId: string
): Promise<AssetMatchResult | null> {
  const ctx = await loadAnalysisContext(analysisId);
  if (!ctx) return null;
  if (!ctx.itglue_org_id) {
    return {
      ticketNumber: ctx.ticket_number,
      organizationId: null,
      organizationName: null,
      flexibleAssets: [],
      configurations: [],
    };
  }

  const terms = fingerprintTerms(ctx.fingerprint);
  if (terms.length === 0) {
    return {
      ticketNumber: ctx.ticket_number,
      organizationId: ctx.itglue_org_id,
      organizationName: ctx.itglue_org_name,
      flexibleAssets: [],
      configurations: [],
    };
  }

  const [flexCandidates, configCandidates] = await Promise.all([
    loadFlexAssetCandidates(ctx.itglue_org_id),
    loadConfigurationCandidates(ctx.itglue_org_id),
  ]);

  const flexHits: MatchedFlexibleAsset[] = [];
  for (const c of flexCandidates) {
    let bestScore = 0;
    let bestTerm = '';
    for (const term of terms) {
      const s = scoreMatch(c.name, term);
      if (s > bestScore) {
        bestScore = s;
        bestTerm = term;
      }
    }
    if (bestScore > 0) {
      flexHits.push({
        id: c.id,
        name: c.name,
        type_name: c.type_name,
        score: bestScore,
        matched_term: bestTerm,
      });
    }
  }

  const configHits: MatchedConfiguration[] = [];
  for (const c of configCandidates) {
    let bestScore = 0;
    let bestTerm = '';
    for (const term of terms) {
      const sName = scoreMatch(c.name, term);
      const sHost = scoreMatch(c.hostname, term);
      const s = Math.max(sName, sHost);
      if (s > bestScore) {
        bestScore = s;
        bestTerm = term;
      }
    }
    if (bestScore > 0) {
      configHits.push({
        id: c.id,
        name: c.name,
        hostname: c.hostname,
        type_name: c.type_name,
        score: bestScore,
        matched_term: bestTerm,
      });
    }
  }

  const sortedFlex = dedupeByKey(flexHits)
    .sort((a, b) => b.score - a.score)
    .slice(0, PER_KIND_LIMIT);
  const sortedConfig = dedupeByKey(configHits)
    .sort((a, b) => b.score - a.score)
    .slice(0, PER_KIND_LIMIT);

  return {
    ticketNumber: ctx.ticket_number,
    organizationId: ctx.itglue_org_id,
    organizationName: ctx.itglue_org_name,
    flexibleAssets: sortedFlex,
    configurations: sortedConfig,
  };
}

export const _MATCHER_INTERNALS = { scoreMatch, fingerprintTerms };
