# Copyright (c) 2026 Muhammad Husnain
# This file is part of SymDex.
# License: See LICENSE file in the project root.

from __future__ import annotations

import os
import sqlite3
from math import ceil

DEFAULT_TOKENIZER = "o200k_base"

try:
    import tiktoken  # type: ignore
except ImportError:  # pragma: no cover - fallback for environments without the library
    tiktoken = None  # type: ignore


def _fallback_token_count(text: str) -> int:
    """Conservative approximate token count when tokenizer support is unavailable."""
    if not text:
        return 0
    return max(1, ceil(len(text) / 4))


def count_lines_of_code(text: str) -> int:
    """Approximate non-blank, non-comment lines in a source text block."""
    if not text:
        return 0
    line_count = 0
    in_block_comment = False
    for raw_line in text.splitlines():
        stripped = raw_line.strip()
        if not stripped:
            continue
        if in_block_comment:
            if "*/" in stripped or stripped.endswith("'''") or stripped.endswith('"""'):
                in_block_comment = False
            continue
        if stripped.startswith(("#", "//", "--")):
            continue
        if stripped.startswith(("/*", "<!--", '"""', "'''")):
            if not (
                stripped.endswith("*/")
                or stripped.endswith("-->")
                or stripped.endswith('"""')
                or stripped.endswith("'''")
            ):
                in_block_comment = True
            continue
        line_count += 1
    return line_count


def count_token_metrics(text: str, tokenizer: str = DEFAULT_TOKENIZER) -> dict:
    """Count tokens with the default tokenizer and mark whether the result is approximate."""
    if tiktoken is not None:
        try:
            encoding = tiktoken.get_encoding(tokenizer)
            return {
                "tokenizer": tokenizer,
                "token_count": len(encoding.encode(text)),
                "approximate": False,
            }
        except Exception:  # noqa: BLE001
            pass
    return {
        "tokenizer": tokenizer,
        "token_count": _fallback_token_count(text),
        "approximate": True,
    }


def build_search_roi_summary(
    baseline_text: str,
    result_text: str,
    files_searched: int,
    lines_searched: int,
    tokenizer: str = DEFAULT_TOKENIZER,
) -> dict:
    """Return a compact token-savings summary for a successful search."""
    without = count_token_metrics(baseline_text, tokenizer=tokenizer)
    with_symdex = count_token_metrics(result_text, tokenizer=tokenizer)
    estimated_saved = max(0, without["token_count"] - with_symdex["token_count"])
    return {
        "files_searched": files_searched,
        "lines_searched": lines_searched,
        "tokenizer": tokenizer,
        "approximate": bool(without["approximate"] or with_symdex["approximate"]),
        "estimated_tokens_without_symdex": without["token_count"],
        "estimated_tokens_with_symdex": with_symdex["token_count"],
        "estimated_tokens_saved": estimated_saved,
    }


def build_search_roi_summary_from_rows(
    conn: sqlite3.Connection,
    repo: str,
    rows: list[dict],
    repo_root: str,
    result_kind: str,
    tokenizer: str = DEFAULT_TOKENIZER,
) -> dict | None:
    """Build ROI summary from search result rows and repo files."""
    unique_files = sorted({row["file"] for row in rows if row.get("file")})
    if not unique_files:
        return None

    baseline_parts: list[str] = []
    result_parts: list[str] = []
    lines_searched = 0

    for file_path in unique_files:
        abs_path = os.path.join(repo_root, file_path)
        try:
            with open(abs_path, "r", encoding="utf-8", errors="ignore") as fh:
                file_text = fh.read()
        except OSError:
            continue
        baseline_parts.append(file_text)
        lines_searched += count_lines_of_code(file_text)

    if result_kind == "text":
        result_parts.extend(row.get("text", "") for row in rows if row.get("text"))
    else:
        for row in rows:
            file_path = row.get("file")
            if not file_path:
                continue
            abs_path = os.path.join(repo_root, file_path)
            try:
                with open(abs_path, "rb") as fh:
                    start_byte = int(row.get("start_byte", 0))
                    end_byte = int(row.get("end_byte", start_byte))
                    fh.seek(start_byte)
                    snippet = fh.read(max(0, end_byte - start_byte)).decode("utf-8", errors="ignore")
            except OSError:
                snippet = row.get("name", "")
            result_parts.append(snippet)

    return build_search_roi_summary(
        baseline_text="\n".join(baseline_parts),
        result_text="\n".join(result_parts),
        files_searched=len(unique_files),
        lines_searched=lines_searched,
        tokenizer=tokenizer,
    )
