import json
import os
from typing import (
    Any,
    Optional,
    TypeVar,
)

from pydantic import BaseModel

from galaxy.util.hash_util import md5_hash_str

RAW_CACHED_JSON = dict[str, Any]


def hash_model(model_class: type[BaseModel]) -> str:
    return md5_hash_str(json.dumps(model_class.model_json_schema()))


MODEL_HASHES: dict[type[BaseModel], str] = {}


M = TypeVar("M", bound=BaseModel)


def ensure_model_has_hash(model_class: type[BaseModel]) -> None:
    if model_class not in MODEL_HASHES:
        MODEL_HASHES[model_class] = hash_model(model_class)


class ModelCache:
    _cache_directory: str

    def __init__(self, cache_directory: str):
        if not os.path.exists(cache_directory):
            os.makedirs(cache_directory)
        self._cache_directory = cache_directory

    def _cache_target(self, model_class: type[M], tool_id: str, tool_version: str) -> str:
        ensure_model_has_hash(model_class)
        # consider breaking this into multiple directories...
        cache_target = os.path.join(self._cache_directory, MODEL_HASHES[model_class], tool_id, tool_version)
        return cache_target

    def get_cache_entry_for(self, model_class: type[M], tool_id: str, tool_version: str) -> Optional[M]:
        cache_target = self._cache_target(model_class, tool_id, tool_version)
        if not os.path.exists(cache_target):
            return None
        with open(cache_target) as f:
            return model_class.model_validate(json.load(f))

    def has_cached_entry_for(self, model_class: type[M], tool_id: str, tool_version: str) -> bool:
        cache_target = self._cache_target(model_class, tool_id, tool_version)
        return os.path.exists(cache_target)

    def insert_cache_entry_for(self, model_object: M, tool_id: str, tool_version: str) -> None:
        cache_target = self._cache_target(model_object.__class__, tool_id, tool_version)
        parent_directory = os.path.dirname(cache_target)
        if not os.path.exists(parent_directory):
            os.makedirs(parent_directory)
        with open(cache_target, "w") as f:
            json.dump(model_object.dict(), f)
