"""Typed result objects returned by :class:`rclone.Rclone`."""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, List, Optional


def _to_int(value: str) -> int:
    try:
        return int(value)
    except (TypeError, ValueError):
        return -1


def _to_datetime(value: str) -> Optional[datetime]:
    """Parse rclone's ``YYYY-MM-DD HH:MM:SS[.fraction]`` timestamps."""
    value = value.strip()
    if "." in value:
        head, frac = value.split(".", 1)
        value = f"{head}.{frac[:6]}"  # nanoseconds -> microseconds
        fmt = "%Y-%m-%d %H:%M:%S.%f"
    else:
        fmt = "%Y-%m-%d %H:%M:%S"
    try:
        return datetime.strptime(value, fmt)
    except ValueError:
        return None


@dataclass
class Stats:
    """A snapshot of an rclone transfer's progress.

    Built from the ``stats`` object found in rclone's JSON log lines
    (``--use-json-log --stats <interval>``).
    """

    bytes: int = 0
    total_bytes: int = 0
    speed: float = 0.0
    eta: Optional[float] = None
    transfers: int = 0
    total_transfers: int = 0
    checks: int = 0
    total_checks: int = 0
    errors: int = 0
    elapsed_time: float = 0.0
    fatal_error: bool = False
    retry_error: bool = False
    raw: Dict[str, Any] = field(default_factory=dict)

    @property
    def percentage(self) -> float:
        """Completion as a 0-100 float (0.0 when the total is unknown)."""
        if not self.total_bytes:
            return 0.0
        return round(self.bytes / self.total_bytes * 100, 2)

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "Stats":
        def _int(key: str) -> int:
            return int(data.get(key) or 0)

        def _float(key: str) -> float:
            return float(data.get(key) or 0.0)

        return cls(
            bytes=_int("bytes"),
            total_bytes=_int("totalBytes"),
            speed=_float("speed"),
            eta=data.get("eta"),
            transfers=_int("transfers"),
            total_transfers=_int("totalTransfers"),
            checks=_int("checks"),
            total_checks=_int("totalChecks"),
            errors=_int("errors"),
            elapsed_time=_float("elapsedTime"),
            fatal_error=bool(data.get("fatalError", False)),
            retry_error=bool(data.get("retryError", False)),
            raw=dict(data),
        )


@dataclass
class SizeResult:
    """Result of :meth:`rclone.Rclone.size` (``rclone size --json``)."""

    count: int
    bytes: int
    sizeless: int = 0

    @property
    def total_objects(self) -> int:
        """Alias for :attr:`count` (matches the legacy API)."""
        return self.count

    @property
    def total_size(self) -> int:
        """Alias for :attr:`bytes` (matches the legacy API)."""
        return self.bytes


@dataclass
class RcloneResult:
    """The outcome of running a single rclone command."""

    command: List[str]
    returncode: int
    stdout: str = ""
    stderr: str = ""
    stats: Optional[Stats] = None

    @property
    def success(self) -> bool:
        return self.returncode == 0

    def __bool__(self) -> bool:
        return self.success


@dataclass
class Entry:
    """A parsed listing entry from :meth:`Rclone.lsd` or :meth:`Rclone.lsl`."""

    name: str
    size: int
    mod_time: Optional[datetime]
    is_dir: bool
    count: Optional[int] = None  # object count for dirs (-1 when rclone can't tell)
    raw: str = ""

    @classmethod
    def from_lsd_line(cls, line: str) -> "Entry":
        # rclone lsd: "<size> <date> <time> <count> <name>"
        parts = line.split(None, 4)
        if len(parts) == 5:
            size, date, time, count, name = parts
            return cls(
                name=name,
                size=_to_int(size),
                mod_time=_to_datetime(f"{date} {time}"),
                is_dir=True,
                count=_to_int(count),
                raw=line,
            )
        return cls(name=line.strip(), size=-1, mod_time=None, is_dir=True, raw=line)

    @classmethod
    def from_lsl_line(cls, line: str) -> "Entry":
        # rclone lsl: "<size> <date> <time> <name>"
        parts = line.split(None, 3)
        if len(parts) == 4:
            size, date, time, name = parts
            return cls(
                name=name,
                size=_to_int(size),
                mod_time=_to_datetime(f"{date} {time}"),
                is_dir=False,
                raw=line,
            )
        return cls(name=line.strip(), size=-1, mod_time=None, is_dir=False, raw=line)
