import abc
import json
from enum import IntEnum
from dataclasses import dataclass
from pathlib import Path
from typing import TypedDict, Optional, Callable
from threading import Lock

import polars as pl

from .enumerations import SyncStatus, LoadTrigger


@dataclass
class SyncResult:
    filename: str
    status: SyncStatus
    latest_tag_or_error: str


class DataSource(abc.ABC):
    """ 数据源 """
    @abc.abstractmethod
    def load(self, key: str) -> list[SyncResult]:
        """ 加载数据 """
        ...


class FileType(IntEnum):
    """ 文件类型 """
    PARQUET = 0 # parquet文件
    DELTALAKE = 1 # deltalake文件

        
@dataclass
class File:
    """ 文件 """
    key: str
    file_type: FileType = FileType.PARQUET

    def get_local_path(self, root_path: Path | None = None) -> Path:
        """ 获取本地路径 """
        if not root_path:
            root_path = Path("./data")
        return root_path / self.key

    def output_lazyframe(self, root_path: Path | None = None) -> pl.LazyFrame:
        """ 输出懒惰框架（始终读本地文件；S3 行情由 LoaderWorker 后台预拉取写入本地）"""
        while True:
            try:
                match self.file_type:
                    case FileType.PARQUET:
                        return pl.scan_parquet(self.get_local_path(root_path))
                    case FileType.DELTALAKE:
                        return pl.scan_delta(self.get_local_path(root_path))
                    case _:
                        raise ValueError(f"不支持的文件类型: {self.file_type}")
            except IOError:
                continue

    def output_eager_lazyframe(self, root_path: Path | None = None) -> pl.LazyFrame:
        """ 即时读取后转 LazyFrame（适用于频繁更新的小文件，避免 scan→collect 竞态）
        catch Exception 而非仅 IOError：parquet 竞态抛出 ComputeError，非 IOError """
        while True:
            try:
                return pl.read_parquet(self.get_local_path(root_path)).lazy()
            except Exception:
                continue
            

@dataclass
class LoadRule:
    """ 加载规则 """
    file: File
    tigger: LoadTrigger
    datasource: DataSource
    interval: int | None = None
    is_stop: Callable[[File], bool] | None = None


class Metadata(TypedDict):
    etags: dict[str, str]


class SyncMetadata:
    """
        同步元数据
    """
    METADATA_FILENAME = ".sync_metadata.json"
    
    def __init__(self, local_data_dir: Path) -> None:
        self.lock = Lock()
        self.metadata_file = local_data_dir / self.METADATA_FILENAME
        self.metadata = self._load_metadata()
    
    def _load_metadata(self) -> Metadata:
        """
            加载元数据
            @return: 元数据
        """
        with self.lock:
            if self.metadata_file.exists():
                with open(self.metadata_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            else:
                return {"etags": {}}
        
    def _save_metadata(self) -> None:
        """
            保存元数据
        """
        with open(self.metadata_file, 'w', encoding='utf-8') as f:
            json.dump(self.metadata, f, ensure_ascii=False, indent=2)
        
    def get_file_etag(self, filename: str) -> Optional[str]:
        """
            获取文件ETAG
            @param filename: 文件名
            @return: 文件ETAG
        """
        return self.metadata.get('etags', {}).get(filename, None)
    
    def set_file_etag(self, filename: str, etag: str) -> None:
        """
            设置文件ETAG
            @param filename: 文件名
            @param etag: 文件ETAG
        """
        with self.lock:
            self.metadata['etags'][filename] = etag
            self._save_metadata()
        
    def delete_file_etag(self, filename: str) -> None:
        """
            删除文件ETAG
            @param filename: 文件名
        """
        with self.lock:
            self.metadata['etags'].pop(filename, None)
            self._save_metadata()
