"""Discord bot — čisté helpery (custom_id, výběry → patch, deadline z voleb),
bez sítě/discord lib."""

from __future__ import annotations

import unittest
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

from services.discord_bot import (
    action_to_patch,
    choice_label,
    departure_choice_to_deadline,
    parse_custom_id,
    select_to_patch,
)

_PRAGUE = ZoneInfo("Europe/Prague")
# 2026-06-12 je pátek; 10:00 UTC = 12:00 Europe/Prague (CEST)
_NOW = datetime(2026, 6, 12, 10, 0, tzinfo=timezone.utc)


def _prague(dt: datetime) -> str:
    return dt.astimezone(_PRAGUE).strftime("%Y-%m-%d %H:%M")


class ParseCustomIdTests(unittest.TestCase):
    def test_valid_selects(self) -> None:
        self.assertEqual(
            parse_custom_id("ev:2:ev-charger-1:dep"), (2, "ev-charger-1", "dep")
        )
        self.assertEqual(
            parse_custom_id("ev:2:ev-charger-1:tgt"), (2, "ev-charger-1", "tgt")
        )

    def test_valid_legacy_buttons(self) -> None:
        self.assertEqual(
            parse_custom_id("ev:2:ev-charger-1:h2"), (2, "ev-charger-1", "h2")
        )

    def test_invalid(self) -> None:
        for bad in ("", "ev:2:x:jump", "foo:1:c:h2", "ev:abc:c:h2", "ev:1:c:dep:x"):
            self.assertIsNone(parse_custom_id(bad))


class DepartureChoiceTests(unittest.TestCase):
    """Absolutní deadline z výběru „Kdy odjíždíš?" (Europe/Prague)."""

    def test_h2(self) -> None:
        dl = departure_choice_to_deadline("h2", now=_NOW)
        self.assertEqual(_prague(dl), "2026-06-12 14:00")

    def test_h4(self) -> None:
        dl = departure_choice_to_deadline("h4", now=_NOW)
        self.assertEqual(_prague(dl), "2026-06-12 16:00")

    def test_today18_before_18(self) -> None:
        dl = departure_choice_to_deadline("today18", now=_NOW)  # 12:00 Prague
        self.assertEqual(_prague(dl), "2026-06-12 18:00")

    def test_today18_after_18_rolls_to_next_day(self) -> None:
        late = datetime(2026, 6, 12, 17, 30, tzinfo=timezone.utc)  # 19:30 Prague
        dl = departure_choice_to_deadline("today18", now=late)
        self.assertEqual(_prague(dl), "2026-06-13 18:00")

    def test_tomorrow7_crosses_midnight(self) -> None:
        # 23:30 Prague v pátek → zítra (sobota) 07:00, tj. +7,5 h
        late = datetime(2026, 6, 12, 21, 30, tzinfo=timezone.utc)
        dl = departure_choice_to_deadline("tomorrow7", now=late)
        self.assertEqual(_prague(dl), "2026-06-13 07:00")

    def test_tomorrow12(self) -> None:
        dl = departure_choice_to_deadline("tomorrow12", now=_NOW)
        self.assertEqual(_prague(dl), "2026-06-13 12:00")

    def test_monday7_from_friday_allows_over_48h(self) -> None:
        # explicitní volba smí přes 48h limit fn_ev_session_defaults
        dl = departure_choice_to_deadline("monday7", now=_NOW)  # pátek 12:00
        self.assertEqual(_prague(dl), "2026-06-15 07:00")
        self.assertGreater((dl - _NOW).total_seconds(), 48 * 3600)

    def test_monday7_on_monday_before_7_is_today(self) -> None:
        mon_early = datetime(2026, 6, 15, 3, 0, tzinfo=timezone.utc)  # po 05:00
        dl = departure_choice_to_deadline("monday7", now=mon_early)
        self.assertEqual(_prague(dl), "2026-06-15 07:00")

    def test_monday7_on_monday_after_7_is_next_week(self) -> None:
        mon_late = datetime(2026, 6, 15, 8, 0, tzinfo=timezone.utc)  # po 10:00
        dl = departure_choice_to_deadline("monday7", now=mon_late)
        self.assertEqual(_prague(dl), "2026-06-22 07:00")

    def test_unknown_choice_raises(self) -> None:
        with self.assertRaises(ValueError):
            departure_choice_to_deadline("never", now=_NOW)


class SelectPatchTests(unittest.TestCase):
    """Mapování výběrů na patch payload pro fn_ev_session_apply_patch."""

    def test_dep_patches_only_deadline(self) -> None:
        p = select_to_patch("dep", "today18", now=_NOW, soc_at_connect=55.0)
        self.assertEqual(set(p), {"target_deadline"})
        self.assertIn("2026-06-12T18:00", p["target_deadline"])
        self.assertIn("+02:00", p["target_deadline"])  # Europe/Prague (CEST)

    def test_tgt_patches_only_target(self) -> None:
        for value, expected in (("30", 30.0), ("50", 50.0), ("70", 70.0), ("100", 100.0)):
            p = select_to_patch("tgt", value, now=_NOW, soc_at_connect=55.0)
            self.assertEqual(p, {"target_soc_pct": expected})

    def test_tgt_stop_targets_connect_soc(self) -> None:
        p = select_to_patch("tgt", "stop", now=_NOW, soc_at_connect=42.5)
        self.assertEqual(p, {"target_soc_pct": 42.5})

    def test_tgt_stop_without_soc(self) -> None:
        p = select_to_patch("tgt", "stop", now=_NOW, soc_at_connect=None)
        self.assertEqual(p, {"target_soc_pct": 0.0})

    def test_unknown_kind_raises(self) -> None:
        with self.assertRaises(ValueError):
            select_to_patch("foo", "30", now=_NOW, soc_at_connect=None)

    def test_labels(self) -> None:
        self.assertEqual(choice_label("dep", "monday7"), "odjezd pondělí ráno 7:00")
        self.assertEqual(choice_label("tgt", "70"), "cíl 70 %")
        self.assertEqual(choice_label("tgt", "stop"), "nenabíjet")


class LegacyActionPatchTests(unittest.TestCase):
    """Legacy tlačítka starších zpráv (h2/h4/morning/full/stop)."""

    def _patch(self, action: str, **kw):
        return action_to_patch(
            action,
            now=_NOW,
            soc_at_connect=kw.get("soc", 55.0),
            default_deadline_hour=kw.get("hour", 7),
        )

    def test_h2_deadline(self) -> None:
        p = self._patch("h2")
        self.assertIn("2026-06-12T12:00", p["target_deadline"])

    def test_morning_next_occurrence(self) -> None:
        p = self._patch("morning", hour=7)
        # 12:00 Prague > 7:00 → zítra 7:00 Prague
        self.assertIn("2026-06-13T07:00", p["target_deadline"])

    def test_morning_today_if_before(self) -> None:
        early = datetime(2026, 6, 12, 2, 0, tzinfo=timezone.utc)  # 4:00 Prague
        p = action_to_patch("morning", now=early, soc_at_connect=50, default_deadline_hour=7)
        self.assertIn("2026-06-12T07:00", p["target_deadline"])

    def test_full(self) -> None:
        p = self._patch("full")
        self.assertEqual(p["target_soc_pct"], 100)

    def test_stop_targets_connect_soc(self) -> None:
        p = self._patch("stop", soc=42.5)
        self.assertEqual(p["target_soc_pct"], 42.5)


if __name__ == "__main__":
    unittest.main()
