import hashlib
import json
import logging
import os
import time
from pathlib import Path
from typing import Any, Optional

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)


class FileCache:
    def __init__(self, directory: str, size_limit: Optional[int] = None):
        self.directory = Path(directory)
        self.directory.mkdir(parents=True, exist_ok=True)
        self.size_limit = size_limit
        self.current_size = 0
        self._update_current_size()
        logger.debug(
            f'FileCache initialized with directory: {self.directory}, size_limit: {self.size_limit}, current_size: {self.current_size}'
        )

    def _get_file_path(self, key: str) -> Path:
        hashed_key = hashlib.sha256(key.encode()).hexdigest()
        return self.directory / f'{hashed_key}.json'

    def _update_current_size(self):
        self.current_size = sum(
            f.stat().st_size for f in self.directory.glob('*.json') if f.is_file()
        )
        logger.debug(f'Current size updated: {self.current_size}')

    def set(self, key: str, value: Any) -> None:
        file_path = self._get_file_path(key)
        content = json.dumps({'key': key, 'value': value})
        content_size = len(content.encode('utf-8'))
        logger.debug(f'Setting key: {key}, content_size: {content_size}')

        if self.size_limit is not None:
            if file_path.exists():
                old_size = file_path.stat().st_size
                size_diff = content_size - old_size
                logger.debug(
                    f'Existing file: old_size: {old_size}, size_diff: {size_diff}'
                )
                if size_diff > 0:
                    while (
                        self.current_size + size_diff > self.size_limit
                        and len(self) > 1
                    ):
                        logger.debug(
                            f'Evicting oldest (existing file case): current_size: {self.current_size}, size_limit: {self.size_limit}'
                        )
                        self._evict_oldest(file_path)
            else:
                while (
                    self.current_size + content_size > self.size_limit and len(self) > 1
                ):
                    logger.debug(
                        f'Evicting oldest (new file case): current_size: {self.current_size}, size_limit: {self.size_limit}'
                    )
                    self._evict_oldest(file_path)

        if file_path.exists():
            self.current_size -= file_path.stat().st_size
            logger.debug(
                f'Existing file removed from current_size: {self.current_size}'
            )

        with open(file_path, 'w') as f:
            f.write(content)

        self.current_size += content_size
        logger.debug(f'File written, new current_size: {self.current_size}')
        os.utime(
            file_path, (time.time(), time.time())
        )  # Update access and modification time

    def _evict_oldest(self, exclude_path: Optional[Path] = None):
        oldest_file = min(
            (
                f
                for f in self.directory.glob('*.json')
                if f.is_file() and f != exclude_path
            ),
            key=os.path.getmtime,
        )
        evicted_size = oldest_file.stat().st_size
        self.current_size -= evicted_size
        os.remove(oldest_file)
        logger.debug(
            f'Evicted file: {oldest_file}, size: {evicted_size}, new current_size: {self.current_size}'
        )

    def get(self, key: str, default: Any = None) -> Any:
        file_path = self._get_file_path(key)
        if not file_path.exists():
            logger.debug(f'Get: Key not found: {key}')
            return default
        with open(file_path, 'r') as f:
            data = json.load(f)
            os.utime(file_path, (time.time(), time.time()))  # Update access time
            logger.debug(f'Get: Key found: {key}')
            return data['value']

    def delete(self, key: str) -> None:
        file_path = self._get_file_path(key)
        if file_path.exists():
            deleted_size = file_path.stat().st_size
            self.current_size -= deleted_size
            os.remove(file_path)
            logger.debug(
                f'Deleted key: {key}, size: {deleted_size}, new current_size: {self.current_size}'
            )

    def clear(self) -> None:
        for item in self.directory.glob('*.json'):
            if item.is_file():
                os.remove(item)
        self.current_size = 0
        logger.debug('Cache cleared')

    def __contains__(self, key: str) -> bool:
        exists = self._get_file_path(key).exists()
        logger.debug(f'Contains check: {key}, result: {exists}')
        return exists

    def __len__(self) -> int:
        length = sum(1 for _ in self.directory.glob('*.json') if _.is_file())
        logger.debug(f'Cache length: {length}')
        return length

    def __iter__(self):
        for file in self.directory.glob('*.json'):
            if file.is_file():
                with open(file, 'r') as f:
                    data = json.load(f)
                    logger.debug(f"Yielding key: {data['key']}")
                    yield data['key']

    def __getitem__(self, key: str) -> Any:
        return self.get(key)

    def __setitem__(self, key: str, value: Any) -> None:
        self.set(key, value)
