from imports import *
import os
import json
import re
from pathlib import Path
from typing import Any, Dict, List, Optional

from abstract_utilities import (
    safe_read_from_json,
    safe_dump_to_json,
    make_list,
    get_any_value,
    capitalize,
    # assuming these exist in abstract_utilities:
    # eatAll,
    # get_closest_match_from_list,
)

CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"


def split_caps(string: str) -> List[str]:
    """
    Split a string on capital letters, preserving order.

    Example:
        "uploadDate" -> ["upload", "date"]
    """
    parts = [""]
    for char in string:
        if char in CAPS:
            parts.append("")
        parts[-1] += char.lower()
    return [part for part in parts if part]


def make_underscore(string: str) -> str:
    """
    Convert CamelCase / mixedCase -> snake_case-ish.
    """
    parts = split_caps(string)
    return "_".join(parts).lower()


def get_all_string(strings: Any) -> List[str]:
    """
    Generate a list of variant key names for a given string or list of strings:
    - original
    - underscore version
    - Capitalized underscore version
    - split parts
    """
    strings = make_list(strings)
    all_strings: List[str] = []

    for s in strings:
        s = str(s)
        split_string = split_caps(s)
        underscore_str = make_underscore(s)
        capitalize_str = capitalize(underscore_str)
        all_strings.extend([s, underscore_str, capitalize_str] + split_string)

    return all_strings


def get_key_tree(data: Dict[str, Any]) -> Dict[str, Any]:
    branch: Dict[str, Any] = {}
    for key, value in data.items():
        if isinstance(value, dict):
            branch[key] = get_key_tree(value)
        else:
            branch[key] = None
    return branch


def get_key_paths(data: Dict[str, Any], prefix: str = "", paths: Optional[List[str]] = None) -> List[str]:
    if paths is None:
        paths = []
    for key, value in data.items():
        path = f"{prefix}.{key}" if prefix else key
        paths.append(path)
        if isinstance(value, dict):
            get_key_paths(value, path, paths)
    return paths


def get_all_keys(data: Any, out: Optional[Dict[str, List[str]]] = None) -> Dict[str, List[str]]:
    if out is None:
        out = {}

    if not isinstance(data, dict):
        return out

    for key, value in data.items():
        if isinstance(value, dict):
            out[key] = list(value.keys())
            get_all_keys(value, out)

    return out


def search_keys_nested(data: Any, query: str, path: Optional[List[str]] = None,
                       results: Optional[Dict[str, List[str]]] = None) -> Dict[str, List[str]]:
    """
    Search all dictionary keys (recursively) for matches to `query`
    and categorize results by the branch where they occur.

    Returns:
        {
            "a.b.c": ["matching_key1", "matching_key2"],
            ...
        }
    """
    if results is None:
        results = {}

    if path is None:
        path = []

    if not isinstance(data, dict):
        return results

    for key, value in data.items():
        current_path = path + [key]

        # Check if key matches the search text
        if query.lower() in key.lower():
            branch = ".".join(path) or "<root>"
            results.setdefault(branch, []).append(key)

        # Recurse into dicts
        if isinstance(value, dict):
            search_keys_nested(value, query, current_path, results)

        # Recurse into lists of dicts
        elif isinstance(value, list):
            for idx, item in enumerate(value):
                if isinstance(item, dict):
                    list_path = current_path + [f"[{idx}]"]
                    search_keys_nested(item, query, list_path, results)

    return results


def get_by_path(data: Any, path: str) -> Any:
    """
    Resolve a dotted / list-indexed path such as 'a.b[0].c'
    and return the corresponding data value.
    """

    # Break "a.b[0].c" into tokens: ["a", "b", 0, "c"]
    tokens: List[Any] = []
    for part in path.split("."):
        # split off list indexes like "formats[0][1]"
        m = re.findall(r"[^\[\]]+|\[\d+\]", part)
        for seg in m:
            if seg.startswith("[") and seg.endswith("]"):
                idx = int(seg[1:-1])
                tokens.append(idx)
            else:
                tokens.append(seg)

    current = data
    for tok in tokens:
        if isinstance(tok, int):
            current = current[tok]
        else:
            if not isinstance(current, dict):
                return None
            current = current.get(tok)
            if current is None:
                return None
    return current


def get_key_from_dict(comp_key: str, dict_obj: Dict[str, Any]) -> Optional[Dict[str, Any]]:
    """
    Fuzzy match comp_key against nested dict_obj and return the
    'best' candidate with its path/value.
    """
    if not comp_key:
        return None

    keys = list(dict_obj.keys())

    # 1) Exact top-level hit
    if comp_key in keys:
        pathkey = search_keys_nested(dict_obj, comp_key)
        path = list(pathkey.keys())[0] if pathkey else comp_key
        key_head = path.split('.')[0]
        return {
            "count": 1,
            "path": path,
            "key": comp_key,
            "top": key_head,
            "value": dict_obj.get(comp_key),
        }

    # 2) Partial top-level hit
    for key in keys:
        if comp_key in key:
            pathkey = search_keys_nested(dict_obj, key)
            path = list(pathkey.keys())[0] if pathkey else key
            key_head = path.split('.')[0]
            return {
                "count": 1,
                "path": path,
                "key": key,
                "top": key_head,
                "value": dict_obj.get(key),
            }

    # 3) Fully nested fuzzy search
    result = search_keys_nested(dict_obj, comp_key)
    collect_vals: Dict[str, Dict[str, Dict[str, Any]]] = {}
    all_keys: List[str] = []

    for branch_path, values in result.items():
        key_head = branch_path.split('.')[0] if branch_path != "<root>" else "<root>"
        if key_head not in collect_vals:
            collect_vals[key_head] = {}

        for value_key in values:
            # Full path to this value (branch_path + key)
            full_path = (
                value_key if branch_path in ("", "<root>")
                else f"{branch_path}.{value_key}"
            )

            if value_key not in collect_vals[key_head]:
                collect_vals[key_head][value_key] = {
                    "count": 0,
                    "path": full_path,
                    "key": value_key,
                    "top": key_head,
                    "value": get_by_path(dict_obj, full_path),
                }
                all_keys.append(value_key)

            collect_vals[key_head][value_key]["count"] += 1

    if not collect_vals:
        return None

    # choose closest key name, then highest count
    closest_match = get_closest_match_from_list(comp_key, all_keys)
    highest: Optional[Dict[str, Any]] = None

    for _, keys_for_top in collect_vals.items():
        main_vals = keys_for_top.get(closest_match)
        if not main_vals:
            continue
        count = main_vals["count"]
        if count is not None and main_vals.get("value") is not None:
            if highest is None or count > highest["count"]:
                highest = main_vals

    return highest


# The React props "template" you want to infer from the pipeline dict
strings = """videoId={video.videoId || video.video_id || video.id}
thumbnail={video.thumbnail || 'https://clownworld.biz/imgs/no_image.jpg'}
description={video.description || seo.description || 'Check out this video'}
likeCount={video.like_count ?? video.likeCount ?? 0}
repostCount={video.repost_count ?? video.repostCount ?? 0}
commentCount={video.comment_count ?? video.commentCount ?? 0}
duration={video.duration ?? 0}
webpageUrl={video.webpage_url || `https://clownworld.biz`}
filename={video.file_name || null}
keywords={video.keywords || []}
keywords_str={video.keywords_str || ''}
video_url={video.video_url}
optimized_video_url={video.optimized_video_url || video.video_url}
uploader={video.uploader || 'Unknown'}
uploaderId={video.uploader_id || 'Unknown'}
uploadDate={video.upload_date || 'Unknown'}
uploader_url={video.uploader_url || 'Unknown'}
viewCount={video.view_count ?? video.viewCount ?? 0}
file_name={video.file_name || 'video.mp4'}"""


def get_vars(line: str) -> List[str]:
    """
    From a line like:
        'videoId={video.videoId || video.video_id || video.id}'
    return:
        ['videoId', 'videoId', 'video_id', 'id']
    """
    left, _, right = line.partition("=")
    var = eatAll(left, [' ', '\t', '\n', ''])

    right_clean = eatAll(right, ['\n', '\t'])
    # Split on '||' and '??' tokens rather than plain space
    tokens = re.split(r"\|\||\?\?|\s+", right_clean)
    field_names: List[str] = []

    for tok in tokens:
        tok = tok.strip("{}() ")
        if not tok or "." not in tok:
            continue
        # take the last segment after '.', e.g. 'video.video_id' -> 'video_id'
        field_names.append(tok.split(".")[-1])

    return [var] + field_names


def get_values(strings_block: str, dict_obj: Dict[str, Any]) -> Dict[str, Any]:
    """
    For each line in strings_block, try to find the best matching
    value in dict_obj using fuzzy key search.
    """
    values: Dict[str, Any] = {}
    lines = [line for line in strings_block.splitlines() if line.strip()]

    for line in lines:
        candidates = get_vars(line)
        # Expand variants: camelCase, snake_case, etc.
        variant_keys = get_all_string(candidates)
        key_values = []

        for comp_key in set(variant_keys):
            key_from_dict = get_key_from_dict(comp_key, dict_obj)
            if key_from_dict:
                key_values.append(key_from_dict)

        prop_name = candidates[0]
        best: Dict[str, Any] = {}
        for kv in key_values:
            if not best or (
                best.get("count", 0) < kv.get("count", 0)
                and kv.get("value") is not None
            ):
                best = kv

        values[prop_name] = best.get("value")

    return values


if __name__ == "__main__":
    # from abstract_webtools.managers.videoDownloader import *
    url = "https://stackoverflow.com/questions/77230706/using-only-pip-for-installing-spacy-model-en-core-web-sm"
    video_url = "https://www.youtube.com/watch?v=Kazx0cLoB6A&t=15816s"

    # Example: pull your raw pipeline dict
    info = get_pipeline_data(url=video_url, key="info")
    input(info)
    mapped = get_values(STRINGS_VARS, info)
    print(mapped)
    input("Press Enter to exit...")
