"""
Model Sync - Check and wait for model synchronization managed by brslet.

The brslet daemon downloads models and writes status to `.sync_result` file.
Status values: "running", "done", "failed"
"""

import os
import time
from dataclasses import dataclass
from enum import Enum
from typing import Optional, Callable

from ..config import env


class SyncStatus(str, Enum):
    """Model sync status values"""
    RUNNING = "running"
    DONE = "done"
    FAILED = "failed"
    NOT_FOUND = "not_found"
    NO_SYNC_NEEDED = "no_sync_needed"


@dataclass
class ModelSyncStatus:
    """Model sync status information"""
    status: SyncStatus
    message: str
    model_from: str
    model_path: str
    
    @property
    def is_ready(self) -> bool:
        return self.status in (SyncStatus.DONE, SyncStatus.NO_SYNC_NEEDED)
    
    @property
    def is_failed(self) -> bool:
        return self.status == SyncStatus.FAILED


def check_model_sync_status(
    model_path: Optional[str] = None,
    sync_result_file: Optional[str] = None,
) -> ModelSyncStatus:
    """Check current model sync status by reading .sync_result file."""
    model_from = env.model_sync.model_from
    model_path = model_path or env.model_sync.model_path
    sync_result_file = sync_result_file or env.model_sync.sync_result_file
    sync_file_path = os.path.join(model_path, sync_result_file)
    
    # No MODEL_FROM means no sync needed
    if not model_from:
        return ModelSyncStatus(SyncStatus.NO_SYNC_NEEDED, "MODEL_FROM not set", "", model_path)
    
    # Check sync result file
    if not os.path.exists(sync_file_path):
        return ModelSyncStatus(SyncStatus.NOT_FOUND, f"Waiting for {sync_file_path}", model_from, model_path)
    
    # Read status
    try:
        with open(sync_file_path, 'r') as f:
            status_str = f.read().strip().lower()
    except IOError as e:
        return ModelSyncStatus(SyncStatus.NOT_FOUND, f"Read error: {e}", model_from, model_path)
    
    # Map to enum
    status_map = {"done": SyncStatus.DONE, "running": SyncStatus.RUNNING, "failed": SyncStatus.FAILED}
    status = status_map.get(status_str, SyncStatus.RUNNING)
    return ModelSyncStatus(status, status_str, model_from, model_path)


def wait_for_model_sync(
    model_path: Optional[str] = None,
    sync_result_file: Optional[str] = None,
    timeout: Optional[int] = None,
    interval: Optional[int] = None,
    on_status_change: Optional[Callable[[ModelSyncStatus], None]] = None,
    logger=None,
) -> bool:
    """
    Wait for model sync to complete.
    
    Returns True if sync completed successfully, False if failed or timed out.
    """
    from ..utils.log import uvicorn_logger
    logger = logger or uvicorn_logger
    
    # Check if wait is enabled
    if not env.model_sync.wait_enabled:
        status = check_model_sync_status(model_path, sync_result_file)
        if status.is_ready:
            return True
        raise RuntimeError(f"Model sync wait disabled but not ready: {status.message}")
    
    timeout = max(1, timeout if timeout is not None else env.model_sync.wait_timeout)
    interval = max(1, interval if interval is not None else env.model_sync.wait_interval)
    
    start_time = time.time()
    last_status = None
    
    logger.info(f"Waiting for model sync (timeout={timeout}s, interval={interval}s)")
    
    while True:
        status = check_model_sync_status(model_path, sync_result_file)
        
        # Log status changes
        if last_status is None or status.status != last_status.status:
            logger.info(f"Model sync: {status.status.value} - {status.message} (path={status.model_path})")
            if on_status_change:
                on_status_change(status)
            last_status = status
        
        if status.is_ready:
            logger.info("Model sync complete!")
            return True
        
        if status.is_failed:
            logger.error(f"Model sync failed: {status.message}")
            return False
        
        elapsed = time.time() - start_time
        if elapsed >= timeout:
            logger.error(f"Model sync timed out after {elapsed:.1f}s")
            return False
        
        time.sleep(interval)
