import asyncio
import logging
import threading
from pathlib import Path
from typing import Any

import aiofiles
import aiofiles.os
import msgspec

logger = logging.getLogger(__name__)

_file_locks: dict[str, threading.Lock] = {}
_locks_lock = threading.Lock()
_async_locks: dict[str, asyncio.Lock] = {}


def _get_file_lock(file_path: Path) -> threading.Lock:
    path_str = str(file_path.resolve())
    with _locks_lock:
        if path_str not in _file_locks:
            _file_locks[path_str] = threading.Lock()
        return _file_locks[path_str]


def _get_async_lock(file_path: Path) -> asyncio.Lock:
    path_str = str(file_path.resolve())
    if path_str not in _async_locks:
        _async_locks[path_str] = asyncio.Lock()
    return _async_locks[path_str]


def atomic_write_json(file_path: Path, data: Any, indent: int = 2) -> None:
    lock = _get_file_lock(file_path)
    
    with lock:
        file_path.parent.mkdir(parents=True, exist_ok=True)
        tmp_path = file_path.with_suffix(file_path.suffix + '.tmp')
        
        try:
            _ = indent
            with open(tmp_path, 'wb') as f:
                f.write(msgspec.json.encode(data))
                f.flush()
            
            tmp_path.replace(file_path)
            logger.debug(f"Atomically wrote JSON to {file_path}")
            
        except Exception as e:
            if tmp_path.exists():
                try:
                    tmp_path.unlink()
                except OSError as cleanup_error:
                    logger.warning("Couldn't remove temp file %s: %s", tmp_path, cleanup_error)
            logger.error(f"Failed to write JSON to {file_path}: {e}")
            raise


async def atomic_write_json_async(file_path: Path, data: Any, indent: int = 2) -> None:
    lock = _get_async_lock(file_path)
    
    async with lock:
        await aiofiles.os.makedirs(file_path.parent, exist_ok=True)
        tmp_path = file_path.with_suffix(file_path.suffix + '.tmp')
        
        try:
            _ = indent
            content = msgspec.json.encode(data)
            async with aiofiles.open(tmp_path, 'wb') as f:
                await f.write(content)
            
            await asyncio.to_thread(tmp_path.replace, file_path)
            logger.debug(f"Atomically wrote JSON to {file_path}")
            
        except Exception as e:
            try:
                if tmp_path.exists():
                    await aiofiles.os.remove(tmp_path)
            except OSError as cleanup_error:
                logger.warning("Couldn't remove temp file %s: %s", tmp_path, cleanup_error)
            logger.error(f"Failed to write JSON to {file_path}: {e}")
            raise


def read_json(file_path: Path, default: Any = None) -> Any:
    lock = _get_file_lock(file_path)
    
    with lock:
        if not file_path.exists():
            return default
        
        with open(file_path, 'rb') as f:
            return msgspec.json.decode(f.read())


async def read_json_async(file_path: Path, default: Any = None) -> Any:
    lock = _get_async_lock(file_path)
    
    async with lock:
        if not file_path.exists():
            return default
        
        async with aiofiles.open(file_path, 'rb') as f:
            content = await f.read()
            return msgspec.json.decode(content)
