# Copyright 2026, Jiri Eischmann <jiri@eischmann.cz> and the meshy contributors
# SPDX-License-Identifier: GPL-3.0-or-later

"""Path history tracking and smart route selection for private messages."""

from __future__ import annotations

import logging
import time
from dataclasses import dataclass
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from meshy.storage import Storage

log = logging.getLogger(__name__)

WEIGHT_SUCCESS_INCREMENT = 0.5
WEIGHT_FAILURE_DECREMENT = 0.2
MAX_WEIGHT = 5.0


@dataclass
class PathRecord:
    id: int
    contact_key_hex: str
    path_bytes: str
    path_hash_size: int
    hop_count: int
    success_count: int
    failure_count: int
    last_trip_ms: int | None
    route_weight: float
    last_success_at: float | None


class PathHistoryService:
    def __init__(self, storage: Storage):
        self._storage = storage

    def record_result(
        self,
        contact_key_hex: str,
        path_bytes: str,
        path_hash_size: int,
        hop_count: int,
        success: bool,
        trip_ms: int | None = None,
        weight_increment: float | None = None,
    ):
        if weight_increment is None:
            weight_increment = WEIGHT_SUCCESS_INCREMENT if success else -WEIGHT_FAILURE_DECREMENT
        self._storage.upsert_path_record(
            contact_key_hex,
            path_bytes,
            path_hash_size,
            hop_count,
            success,
            trip_ms,
            weight_increment=weight_increment,
            max_weight=MAX_WEIGHT,
        )
        log.debug(
            "Path %s for %s: %s (trip=%s)",
            path_bytes,
            contact_key_hex[:8],
            "success" if success else "failure",
            trip_ms,
        )

    FIRMWARE_DISCOVERY_WEIGHT = 1.0

    def seed_from_contact(
        self,
        contact_key_hex: str,
        path_bytes: str,
        path_hash_size: int,
        hop_count: int,
    ):
        existing = [
            r
            for r in self._storage.get_path_history(contact_key_hex)
            if r["path_bytes"] == path_bytes
        ]
        if existing:
            return
        self._storage.upsert_path_record(
            contact_key_hex,
            path_bytes,
            path_hash_size,
            hop_count,
            success=True,
            initial_weight=self.FIRMWARE_DISCOVERY_WEIGHT,
        )
        log.debug(
            "Seeded path %s for %s from firmware (weight=%.1f)",
            path_bytes,
            contact_key_hex[:8],
            self.FIRMWARE_DISCOVERY_WEIGHT,
        )

    def has_paths(self, contact_key_hex: str) -> bool:
        return bool(self._storage.get_path_history(contact_key_hex))

    def select_path(
        self,
        contact_key_hex: str,
        exclude_paths: list[str] | None = None,
    ) -> PathRecord | None:
        records = [self._dict_to_record(r) for r in self._storage.get_path_history(contact_key_hex)]
        if not records:
            return None

        if exclude_paths:
            candidates = [r for r in records if r.path_bytes not in exclude_paths]
            if not candidates:
                return None
        else:
            candidates = records

        return max(candidates, key=self.score, default=None)

    def score(self, record: PathRecord) -> float:
        total = record.success_count + record.failure_count
        reliability = (record.success_count + 1) / (total + 2)

        if record.last_trip_ms and record.last_trip_ms > 0:
            latency = 1.0 / (1.0 + record.last_trip_ms / 10000.0)
        else:
            latency = 0.5

        if record.last_success_at:
            age_days = (time.time() - record.last_success_at) / 86400.0
            freshness = 1.0 / (1.0 + age_days)
        else:
            freshness = 0.0

        return reliability * 0.6 + latency * 0.2 + freshness * 0.2

    def score_dict(self, d: dict) -> float:
        return self.score(self._dict_to_record(d))

    @staticmethod
    def _dict_to_record(d: dict) -> PathRecord:
        return PathRecord(
            id=d["id"],
            contact_key_hex=d["contact_key_hex"],
            path_bytes=d["path_bytes"],
            path_hash_size=d["path_hash_size"],
            hop_count=d["hop_count"],
            success_count=d["success_count"],
            failure_count=d["failure_count"],
            last_trip_ms=d["last_trip_ms"],
            route_weight=d["route_weight"],
            last_success_at=d["last_success_at"],
        )
