from __future__ import annotations

import time
from dataclasses import asdict, dataclass

from services.mesh.mesh_peer_store import PeerRecord


@dataclass(frozen=True)
class SyncWorkerState:
    last_sync_started_at: int = 0
    last_sync_finished_at: int = 0
    last_sync_ok_at: int = 0
    next_sync_due_at: int = 0
    last_peer_url: str = ""
    last_error: str = ""
    last_outcome: str = "idle"
    current_head: str = ""
    fork_detected: bool = False
    consecutive_failures: int = 0

    def to_dict(self) -> dict[str, object]:
        return asdict(self)


def eligible_sync_peers(records: list[PeerRecord], *, now: float | None = None) -> list[PeerRecord]:
    current_time = int(now if now is not None else time.time())
    candidates = [
        record
        for record in records
        if record.bucket == "sync" and record.enabled and int(record.cooldown_until or 0) <= current_time
    ]

    def _seed_priority(record: PeerRecord) -> int:
        role = str(record.role or "").strip().lower()
        source = str(record.source or "").strip().lower()
        if role == "seed" and source in {"bundle", "bootstrap_promoted"}:
            return 0
        return 1

    return sorted(
        candidates,
        key=lambda record: (
            -int(record.last_sync_ok_at or 0),
            _seed_priority(record),
            int(record.failure_count or 0),
            int(record.added_at or 0),
            record.peer_url,
        ),
    )


def begin_sync(
    state: SyncWorkerState,
    *,
    peer_url: str = "",
    current_head: str = "",
    now: float | None = None,
) -> SyncWorkerState:
    timestamp = int(now if now is not None else time.time())
    return SyncWorkerState(
        last_sync_started_at=timestamp,
        last_sync_finished_at=state.last_sync_finished_at,
        last_sync_ok_at=state.last_sync_ok_at,
        next_sync_due_at=state.next_sync_due_at,
        last_peer_url=peer_url or state.last_peer_url,
        last_error="",
        last_outcome="running",
        current_head=current_head or state.current_head,
        fork_detected=False,
        consecutive_failures=state.consecutive_failures,
    )


def finish_sync(
    state: SyncWorkerState,
    *,
    ok: bool,
    peer_url: str = "",
    current_head: str = "",
    error: str = "",
    fork_detected: bool = False,
    now: float | None = None,
    interval_s: int = 300,
    failure_backoff_s: int = 60,
) -> SyncWorkerState:
    timestamp = int(now if now is not None else time.time())
    if ok:
        return SyncWorkerState(
            last_sync_started_at=state.last_sync_started_at,
            last_sync_finished_at=timestamp,
            last_sync_ok_at=timestamp,
            next_sync_due_at=timestamp + max(0, int(interval_s or 0)),
            last_peer_url=peer_url or state.last_peer_url,
            last_error="",
            last_outcome="ok",
            current_head=current_head or state.current_head,
            fork_detected=bool(fork_detected),
            consecutive_failures=0,
        )

    return SyncWorkerState(
        last_sync_started_at=state.last_sync_started_at,
        last_sync_finished_at=timestamp,
        last_sync_ok_at=state.last_sync_ok_at,
        next_sync_due_at=timestamp + max(0, int(failure_backoff_s or 0)),
        last_peer_url=peer_url or state.last_peer_url,
        last_error=str(error or "").strip(),
        last_outcome="fork" if fork_detected else "error",
        current_head=current_head or state.current_head,
        fork_detected=bool(fork_detected),
        consecutive_failures=state.consecutive_failures + 1,
    )


def finish_solo_sync(
    state: SyncWorkerState,
    *,
    current_head: str = "",
    now: float | None = None,
    interval_s: int = 300,
) -> SyncWorkerState:
    timestamp = int(now if now is not None else time.time())
    return SyncWorkerState(
        last_sync_started_at=state.last_sync_started_at,
        last_sync_finished_at=timestamp,
        last_sync_ok_at=state.last_sync_ok_at,
        next_sync_due_at=timestamp + max(0, int(interval_s or 0)),
        last_peer_url="",
        last_error="",
        last_outcome="solo",
        current_head=current_head or state.current_head,
        fork_detected=False,
        consecutive_failures=0,
    )


def should_run_sync(
    state: SyncWorkerState,
    *,
    now: float | None = None,
) -> bool:
    current_time = int(now if now is not None else time.time())
    if state.last_outcome == "running":
        return False
    return int(state.next_sync_due_at or 0) <= current_time
