/**
 * services/detect.js
 * 3-tier detection: URL pattern → title/snippet keyword → MiniMax fallback.
 *
 * Returns: {
 *   tier: 1 | 2 | 3,
 *   isNcii: boolean,
 *   confidence: number,
 *   reason: string,
 *   aiCallId: number | null,
 *   keywordsMatched: string[],
 * }
 */

'use strict';

const { db } = require('../config/db');
const ai = require('./ai');
const Finding = require('../models/finding');

const KEYWORDS = [
  'porn', 'nude', 'nudes', 'naked', 'sex', 'xxx', 'leaked', 'nsfw',
  'adult', 'xvideos', 'pornhub', 'xhamster', 'redtube', 'onlyfans',
  'fansly', 'cam4', 'chaturbate', 'spankbang', 'motherless', 'rule34',
  'brazzers', 'bangbros', 'mofos', 'realitykings',
  'revenge', 'leak', 'exposed', 'cheating', 'hookup', 'snap',
  'onlyfansleak', 'fanslyleak', 'leaklove', 'leakedzone',
];

function matchesKeyword(text) {
  if (!text) return [];
  const t = String(text).toLowerCase();
  return KEYWORDS.filter(k => t.includes(k));
}

/**
 * Tier 1: URL pattern match. Returns null if no match.
 */
function tier1Url(url) {
  if (!url) return null;
  const matched = matchesKeyword(url);
  if (matched.length) {
    return {
      tier: 1,
      isNcii: true,
      confidence: 0.95,
      reason: `URL keyword match: ${matched.slice(0, 3).join(', ')}`,
      keywordsMatched: matched,
      aiCallId: null,
    };
  }
  return null;
}

/**
 * Tier 2: title/snippet keyword + victim name co-occurrence.
 */
function tier2Text({ title, snippet, victimName }) {
  const hits = [...matchesKeyword(title), ...matchesKeyword(snippet)];
  const victimInText = victimName && (
    (title && title.toLowerCase().includes(victimName.toLowerCase())) ||
    (snippet && snippet.toLowerCase().includes(victimName.toLowerCase()))
  );
  if (hits.length && victimInText) {
    return {
      tier: 2,
      isNcii: true,
      confidence: 0.90,
      reason: `Title/snippet keyword + victim name: ${hits.slice(0, 3).join(', ')}`,
      keywordsMatched: hits,
      aiCallId: null,
    };
  }
  if (hits.length >= 2) {
    // strong keyword signal even without victim name match
    return {
      tier: 2,
      isNcii: true,
      confidence: 0.80,
      reason: `Multiple keyword matches (${hits.length}): ${hits.slice(0, 3).join(', ')}`,
      keywordsMatched: hits,
      aiCallId: null,
    };
  }
  return null;
}

/**
 * Tier 3: MiniMax AI fallback. Minimise calls — only triggered if tier1+2
 * both inconclusive.
 */
async function tier3Ai({ url, title, snippet, imageB64 = null, reason = 'tier1+2 inconclusive' }) {
  const result = await ai.classifyNcii({ url, title, snippet, imageB64 });
  if (result.isNcii && result.confidence >= 0.75) {
    return {
      tier: 3,
      isNcii: true,
      confidence: result.confidence,
      reason: `AI confirmed (${result.confidence.toFixed(2)}): ${result.reason || ''}`.trim(),
      keywordsMatched: [],
      aiCallId: result.ai_call_id,
      tier3Reason: reason,
    };
  }
  return {
    tier: 3,
    isNcii: false,
    confidence: result.confidence,
    reason: `AI dismissed (${result.confidence.toFixed(2)}): ${result.reason || ''}`.trim(),
    keywordsMatched: [],
    aiCallId: result.ai_call_id,
    tier3Reason: reason,
  };
}

/**
 * Master entry: run all tiers in order. Returns the first CONFIRMED result, or
 * the tier3 dismissal, or { isNcii: false, tier: 0, ... } if nothing triggered.
 */
async function detect({ url, title, snippet, victimName, imageB64 = null }) {
  const t1 = tier1Url(url);
  if (t1) return t1;
  const t2 = tier2Text({ title, snippet, victimName });
  if (t2) return t2;
  if (!process.env.MiniMax_API_KEY && !(await hasKey('MiniMax'))) {
    return { tier: 0, isNcii: false, confidence: 0, reason: 'no AI key + tier1/2 inconclusive', keywordsMatched: [], aiCallId: null };
  }
  return tier3Ai({ url, title, snippet, imageB64, reason: 'tier1+2 inconclusive' });
}

async function hasKey(service) {
  const row = db.prepare('SELECT 1 AS x FROM api_keys WHERE service = ? AND is_active = 1 LIMIT 1').get(service);
  return !!row;
}

/**
 * Convenience: detect + persist a finding row, return the row.
 */
async function detectAndPersist({ case_id, user_id, url, page_title, page_snippet, image_hashes = null }) {
  const Case = require('../models/case');
  const c = Case.getById(case_id);
  if (!c) throw new Error('Case not found');

  const result = await detect({
    url,
    title: page_title,
    snippet: page_snippet,
    victimName: c.victim_name,
  });
  const status = result.isNcii ? 'confirmed' : 'dismissed';
  const finding = Finding.create({
    case_id,
    user_id,
    url,
    page_title,
    page_snippet,
    image_hashes,
    tier: result.tier,
    tier3_reason: result.tier3Reason || result.reason,
    ai_call_id: result.aiCallId,
    confidence: result.confidence,
    is_ncii: result.isNcii,
    status,
  });
  return { finding, result };
}

module.exports = { detect, detectAndPersist, KEYWORDS, matchesKeyword };
