# src/pipeline.py
from ..imports import *
from ..modules import *
from .utilities import get_dict_example
def get_thumbnails(video_id: str, video_path: str, thumbnails_path: str):
    """
    Extract thumbnails + OCR → persist into DB JSON.
    """
    print(f"video_id=={video_id}\nvideo_path=={video_path}\nthumbnails_path={thumbnails_path}")
    paths = extract_video_frames_unique(video_path, thumbnails_path, video_id=video_id)
    texts = [ocr_image(p) for p in paths]
    thumbnails = {"paths": paths, "texts": texts}
    upsert_video(video_id, thumbnails=thumbnails)
    return thumbnails

def get_video_id_from_vars(url=None,video_id=None,video_path=None):
    if video_id:
        return video_id
    video_id =   get_video_id(
        url=url,
        video_id=video_id,
        video_path=video_path)
    if video_id:
        return video_id
    if url:
        video_id = get_video_id(url)
    if video_id:
        return video_id
    if video_path:
        path_parts = [part for part in video_path.split(VIDEOS_ROOT_DEFAULT)[-1].split('/') if part]
        if path_parts:
            video_id = path_parts[0]
        if not video_id:
            video_id = generate_video_id(video_path)
    return video_id
def get_video_path_from_vars(url=None,video_id=None,video_path=None):
    if video_path:
        return video_path
    video_path = get_video_filepath(
        url=url,
        video_id=video_id,
        video_path=video_path)
    if video_path:
        return video_path
    video_id = get_video_id_from_vars(url=url,video_id=video_id,video_path=video_path)
    video_path = os.path.join(VIDEOS_ROOT_DEFAULT,video_id,'video.mp4')
    return video_path
def get_video_url_from_vars(url=None,video_id=None,video_path=None):
    if url:
        return url
    video_info = get_video_info(
        url=url,
        video_id=video_id,
        video_path=video_path)
    for key in ['original_url','webpage_url']:
        url = make_list(get_any_value(video_info,key) or None)[0]
        if url:
            return url
    return url
def get_all_meadata(video_id,data):
    base_url = "https://clownworld.biz"
    record = get_video_record(video_id)
    video_info = record.get("info") or {}
    directory = video_info.get("directory")
    meta_file_path = os.path.join(directory, "meta.json")
    metadict = build_seo_meta_from_result(data, base_url=base_url)
    # Make sure all paths inside metadict are URL-style before saving
    metadict_public = convert_paths_to_urls(metadict, base_url=base_url)
    upsert_video(video_id, metatags=metadict_public)
    safe_dump_to_json(data=metadict_public, file_path=meta_file_path)
    return metadict_public
class VideoPipeline:
    def __init__(self, video_url=None, video_id=None, video_path=None, force_refresh=False,videoId=None):
        self.video_url = video_url
        self.video_path = video_path
        self.video_id = video_id or videoId
        if (self.video_path  or self.video_id) and not self.video_url:
            self.video_id = self.video_id or generate_video_id(video_path)
        else:
            self.video_url = get_corrected_url(self.video_url)
            self.video_id = video_id or get_video_id(self.video_url)

        self.registry = infoRegistry()

        self.info = self.registry.get_video_info(
            url=self.video_url,
            video_id=self.video_id,
            video_path=self.video_path,
            force_refresh=force_refresh,
        ) or {}
        self.video_info = self.info.get("info") or {}
        self.video_id = get_video_id_from_vars(url=self.video_url,video_id=self.video_id,video_path=self.video_path)
        self.video_path = get_video_path_from_vars(url=self.video_url,video_id=self.video_id,video_path=self.video_path)
        self.video_url = get_video_url_from_vars(url=self.video_url,video_id=self.video_id,video_path=self.video_path)
        
        
    def get_video_info(self):
        video_mgr =  VideoDownloader(
            url=self.video_url,
            video_id=self.video_id,
            video_path=self.video_path,
            registry=self.registry,
            download_video=False,
        )
        return get_video_info_from_mgr(video_mgr)
    def download_video(self):
        video_mgr = VideoDownloader(
            url=self.video_url,
            registry=self.registry,
            download_video=True,
        )
        return get_video_info_from_mgr(video_mgr)
    def get_file_path(self, key: str):
        """
        Safely fetch a path from self.video_info and tell whether it exists on disk.
        """
        path = self.video_info.get(key)
        if not path:
            logger.info(f"{key} is empty or missing in video_info")
            return None, False

        exists = os.path.isfile(path)
        isexist = "exists" if exists else "does not exist"
        logger.info(f"{key} is {path} and {isexist}")
        return path, exists
    # === CAPTIONS ===
    def get_rawdata(self):
        record = get_video_record(self.video_id) or {}
        rawdata = record.get("rawdata")
        if record and rawdata:
            return record["rawdata"]
        video_info = record.get("info") or {}
        video_url = self.video_url
        directory = video_info.get("directory")
        outtmpl = os.path.join(directory, "video.%(ext)s")
        with yt_dlp.YoutubeDL(build_ydl_opts(outtmpl)) as ydl:
            rawdata = ydl.extract_info(video_url, download=False)
        upsert_video(self.video_id, rawdata=rawdata)
        return rawdata
    # === AUDIO ===
    def ensure_audio(self):
        record = get_video_record(self.video_id) or {}

        audio_path, exists = self.get_file_path("audio_path")
        if record and audio_path and exists:
            audio_format = audio_path.split(".")[-1]
            return audio_path, audio_format

        # Need to (re)create audio
        self.download_video()
        video_path, v_exists = self.get_file_path("video_path")
        if not video_path or not v_exists:
            raise RuntimeError(f"Video file not found for {self.video_id}")

        # Choose a sensible default audio file name if missing
        if not audio_path:
            base, _ = os.path.splitext(video_path)
            audio_path = base + ".wav"

        os.system(
            f"ffmpeg -y -i {video_path} -vn -acodec pcm_s16le -ar 16000 -ac 1 {audio_path}"
        )
        audio_format = audio_path.split(".")[-1]

        upsert_video(self.video_id, audio_path=audio_path, audio_format=audio_format)
        return audio_path, audio_format

    # === WHISPER ===
    def get_whisper(self):
        record = get_video_record(self.video_id) or {}
        whisper_result = record.get("whisper")

        if record and whisper_result:
            return whisper_result

        audio_path, fmt = self.ensure_audio()
        result = whisper_transcribe(audio_path)

        upsert_video(self.video_id, whisper=result)
        return result

    # === CAPTIONS ===
    def get_captions(self):
        record = get_video_record(self.video_id) or {}
        if record and record.get("captions"):
            return record["captions"]

        whisper = self.get_whisper()
        captions = [
            {"start": seg["start"], "end": seg["end"], "text": seg["text"]}
            for seg in whisper.get("segments", [])
        ]

        upsert_video(self.video_id, captions=captions)
        return captions

    # === METADATA ===
    def get_metadata(self):
        record = get_video_record(self.video_id) or {}
        if record and record.get("metadata"):
            return record["metadata"]

        whisper = self.get_whisper()
        captions = self.get_captions()

        text_blob = " ".join([whisper.get("text", "")] + [c["text"] for c in captions])
        title = (
            self.info.get("title")
            or self.video_info.get("title")
            or text_blob[:50]
            or "Untitled"
        )
        summary = get_summarizer_summary(text=text_blob)

        keywords = self.info.get("keywords") or self.video_info.get("keywords") or []
        tags = self.info.get("tags") or self.video_info.get("tags") or []
        extracted_keywords = get_extracted_keywords(text=text_blob) or []
        keywords = make_list(keywords + tags + extracted_keywords)

        category = classify_category(make_list(keywords),title=title,description=summary)
        if len(keywords) >= 10:
            keywords = keywords[:10]

        metadata = {
            "title": title,
            "summary": summary,
            "category": category,
            "keywords": keywords,
        }

        upsert_video(self.video_id, metadata=metadata)
        return metadata

    # === THUMBNAILS ===
    def get_thumbnails(self):
        record = get_video_record(self.video_id) or {}
        if record and record.get("thumbnails"):
            return record["thumbnails"]

        self.download_video()
        video_id = self.video_id
        video_path = self.video_info.get("video_path")
        thumbnails_dir = self.video_info.get("thumbnails_directory")
        
        thumbnails = get_thumbnails(video_id, video_path, thumbnails_dir)
        upsert_video(self.video_id, thumbnails=thumbnails)
        return thumbnails

    # === SEODATA ===
    def get_seodata(self):
        record = get_video_record(self.video_id) or {}
        if record and record.get("seodata"):
            return record["seodata"]
        
        dl = VideoDownloader(url=self.video_url, download_video=True)
        whisper_result = self.get_whisper()
        captions = self.get_captions()

        thumbnails = self.get_thumbnails()
        thumbnails_dir = self.video_info.get("thumbnails_directory")
        thumbnails_paths = thumbnails.get("paths")
        
        metadata = self.get_metadata()
        title = metadata.get("title")
        summary = metadata.get("summary")
        keywords = metadata.get("keywords")
        description = metadata.get("description", summary)

        video_id = self.video_id
        video_path = self.video_info.get("video_path")
        if not (video_path and os.path.isfile(video_path)):
            file_map = get_file_map(video_path, types="video")
            video_paths = file_map.get("video")
            if video_paths:
                video_path = video_paths[0]

        audio_path = self.video_info.get("audio_path")
        if not (audio_path and os.path.isfile(audio_path)):
            file_map = get_file_map(video_path, types="audio")
            audio_paths = file_map.get("audio")
            if audio_paths:
                audio_path = audio_paths[0]

        seodata = get_seo_data(
            video_path=video_path,
            filename=video_id,
            title=title,
            summary=summary,
            description=description,
            keywords=keywords,
            thumbnails_dir=thumbnails_dir,
            thumbnail_paths=thumbnails_paths,
            whisper_result=whisper_result,
            audio_path=audio_path,
            domain="clownworld.biz",
        )

        # Store raw seodata (filesystem paths), frontend will get URL-converted
        upsert_video(self.video_id, seodata=seodata)
        return seodata

    # === AGGREGATED ===
    def get_aggregated_data(self):
        record = get_video_record(self.video_id) or {}
        if record and record.get("aggregated"):
            return record["aggregated"]

        video_info = record.get("info") or {}
        directory = video_info.get("directory")
        aggregated = aggregate_from_base_dir(directory=directory)

        upsert_video(self.video_id, aggregated=aggregated)
        return aggregated

    # === METATAGS / META.JSON ===
    def get_meta_tags(self):
        """
        Build meta tags, persist them, and return a version where
        all filesystem/static paths are converted to public URLs.
        """
        record = get_video_record(self.video_id) or {}
        if record and record.get("metatags"):
            return record["metatags"]
        data = self.get_all_data()
        return get_all_meadata(video_id = self.video_id,data = data)
    # === METATAGS / META.JSON ===
    def get_json_page(self):
        """
        Build meta tags, persist them, and return a version where
        all filesystem/static paths are converted to public URLs.
        """
        record = get_video_record(self.video_id) or {}
        if record and record.get("pagedata"):
            return record["pagedata"]

        video_info = record.get("info") or {}
        directory = video_info.get("directory")
        video_url = video_info.get("video_url")
        self.get_meta_tags()
        pagedata = build_page_json(directory=directory,video_url = video_url,source_video_url = video_url)
        
        return pagedata
    # === METATAGS / META.JSON ===
    def get_extract_fields(self):
        """
        Build meta tags, persist them, and return a version where
        all filesystem/static paths are converted to public URLs.
        """
        video_path = f"{VIDEOS_ROOT_DEFAULT}/{self.video_id}/video.mp4"
        if is_file(video_path):
            video_url = f"https://clownworld.biz/videos/{self.video_id}/video.mp4"
        else:
            video_url = self.video_url
        share_url = f"https://clownworld.biz/videos/?video_id={self.video_id}"
        extractfields = None
        info = self.get_info()
        info_keys = list(info.keys())

        for key in ["pagedata","metatags","seodata","thumbnails","metadata","rawdata"]:
            values = info.get(key)
            if values and isinstance(values,dict):
                nufields = get_values(STRINGS_VARS,values) or {}
                if extractfields == None:
                    nufields["videoId"] = self.video_id
                    nufields["original_url"] = self.video_url
                    nufields["video_url"] = video_url
                    nufields["optimized_video_url"] = self.video_url
                    nufields["share_url"] =share_url
                    extractfields = nufields
                else:    
                    for fkey,fval in nufields.items():
                        extractfields[fkey] = extractfields.get(fkey) or fval
            if isinstance(values,dict):
                nufields = extract_video_fields(values) or {}
                if extractfields == None:
                    extractfields = nufields
                else:    
                    for fkey,fval in nufields.items():
                        extractfields[fkey] = extractfields.get(fkey) or fval
        get_all_meadata(video_id = self.video_id,data = info)
        return extractfields
    # === INFO / ALL ===
    def get_info(self):
        self.get_rawdata()
        return get_video_record(self.video_id, hide_audio=True) or {}

    def get_all_data(self):
        return {
            "info": self.get_info(),
            "rawdata": self.get_rawdata(),
            "whisper": self.get_whisper(),
            "captions": self.get_captions(),
            "metadata": self.get_metadata(),
            "thumbnails": self.get_thumbnails(),
            "seodata": self.get_seodata(),
            "aggregated": self.get_aggregated_data(),
        }

    def get_all(self):
        return {
            "info": self.get_info(),
            "rawdata": self.get_rawdata(),
            "whisper": self.get_whisper(),
            "captions": self.get_captions(),
            "metadata": self.get_metadata(),
            "thumbnails": self.get_thumbnails(),
            "seodata": self.get_seodata(),
            "aggregated": self.get_aggregated_data(),
            "metatags": self.get_meta_tags(),
            "pagedata": self.get_json_page(),
        }
def search_keys_nested(data, query, path=None, results=None):
    """
    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_key_from_dict(comp_key,dict_obj):
    if not comp_key:
        return 
    keys = list(dict_obj.keys())
    if comp_key in keys:
        return comp_key
    for key in keys:
        if comp_key in key:
            return key
    result = search_keys_nested(get_dict_example(), comp_key)
    collect_vals = {}
    all_keys = []
    for key,values in result.items():
        key_head = key.split('.')[0]
        if key_head not in collect_vals:
            collect_vals[key_head]={}
        for value in values:
           if value not in collect_vals[key_head]:
               collect_vals[key_head][value] = 0
               all_keys.append(value)
           collect_vals[key_head][value]+=1
    
    highest = [0,None]
    closest_match = get_closest_match_from_list(comp_key,all_keys)
    for main_key,keys in collect_vals.items():

        count = keys.get(closest_match)
        if count != None and (highest[-1] == None or count > highest[0]):
            highest = [count,main_key]
  
    return highest[-1]
            
def get_pipeline_data(
    url: str=None,
    video_id: str=None,
    key: str | None = None,
    videoId: str=None,
    base_url: str = "https://clownworld.biz",
):
    """
    Public API used by Flask route.

    ANYTHING returned to the client is run through convert_paths_to_urls,
    so React always sees proper URLs, never raw filesystem paths.
    """
    video_id = video_id or videoId
    p = VideoPipeline(video_url=url,video_id=video_id)
    if p.video_url == None and p.video_path == None:
        return 
    pipeline_js = {
        "info": p.get_info,
        "rawdata": p.get_rawdata,
        "extractfields":p.get_extract_fields,
        "download": p.download_video,
        "download_video": p.download_video,
        "extract_audio": p.ensure_audio,
        "whisper": p.get_whisper,
        "captions": p.get_captions,
        "metadata": p.get_metadata,
        "thumbnails": p.get_thumbnails,
        "seodata": p.get_seodata,
        "metatags": p.get_meta_tags,
        "pagedata":p.get_json_page,
        "get_all": p.get_all,
    }
    
    def _call_and_convert(func):
        result = func()
        data = convert_paths_to_urls(result, base_url=base_url)
        return data
    if key:
        key = get_key_from_dict(comp_key=key,dict_obj=pipeline_js)
        func = pipeline_js.get(key)
        if func:
            return _call_and_convert(func)

    # Default: full pipeline
    return _call_and_convert(p.get_all)
