import os
import unittest
from datetime import datetime, timezone
from unittest.mock import patch

from core import msg_builder, tasks
from core.send_state import history_entry_is_strong_confirmed_today
from webui import ops


class SendStateTests(unittest.TestCase):
    def setUp(self):
        self.now = datetime(2026, 7, 10, 14, 0, tzinfo=timezone.utc)
        self.window = {
            "enabled": True,
            "startHour": 10,
            "endHour": 18,
            "scheduleIntervalMinutes": 20,
        }

    def test_strong_confirmation_is_the_only_sent_state(self):
        strong = {
            "sentAt": self.now.isoformat(),
            "status": "confirmed",
            "confirmationLevel": "strong",
            "needsVerification": False,
        }
        weak = {
            "sentAt": self.now.isoformat(),
            "status": "unconfirmed",
            "confirmationLevel": "weak",
            "needsVerification": True,
        }
        legacy = {"sentAt": self.now.isoformat()}

        self.assertTrue(history_entry_is_strong_confirmed_today(strong, self.now))
        self.assertFalse(history_entry_is_strong_confirmed_today(weak, self.now))
        self.assertFalse(history_entry_is_strong_confirmed_today(legacy, self.now))

    def test_unconfirmed_target_is_visible_and_retryable(self):
        user = {
            "username": "demo",
            "unique_id": "demo",
            "targets": ["target"],
            "message_history": {
                "target": {
                    "sentAt": self.now.isoformat(),
                    "status": "unconfirmed",
                    "confirmationLevel": "weak",
                    "needsVerification": True,
                }
            },
            "failure_queue": {
                "target": {
                    "lastAttemptAt": self.now.isoformat(),
                    "category": "send_unconfirmed",
                    "attemptCount": 1,
                }
            },
        }

        status = ops._build_target_status(user, "target", self.now, self.window)

        self.assertEqual("unconfirmed", status["status"])
        self.assertFalse(tasks._target_sent_today(user, "target", self.now))
        self.assertEqual(["target"], tasks._pending_failed_targets(user, self.now))
        self.assertEqual(["target"], tasks._pending_unsent_targets(user, self.now)[0])

    def test_legacy_sent_at_only_record_is_retryable(self):
        user = {
            "targets": ["target"],
            "message_history": {"target": {"sentAt": self.now.isoformat()}},
        }

        status = ops._build_target_status(user, "target", self.now, self.window)

        self.assertEqual("unconfirmed", status["status"])
        self.assertTrue(status["legacyUnverified"])
        self.assertEqual(["target"], tasks._pending_failed_targets(user, self.now))
        self.assertEqual(["target"], tasks._pending_unsent_targets(user, self.now)[0])

    def test_unsent_retry_respects_non_retryable_and_attempt_limit(self):
        user = {
            "targets": ["blocked", "exhausted", "retryable"],
            "failure_queue": {
                "blocked": {
                    "lastAttemptAt": self.now.isoformat(),
                    "category": "protocol_user_blocked",
                    "attemptCount": 1,
                },
                "exhausted": {
                    "lastAttemptAt": self.now.isoformat(),
                    "category": "timeout",
                    "attemptCount": 3,
                },
                "retryable": {
                    "lastAttemptAt": self.now.isoformat(),
                    "category": "timeout",
                    "attemptCount": 2,
                },
            },
        }

        retryable, skipped = tasks._pending_unsent_targets(user, self.now)

        self.assertEqual(["retryable"], retryable)
        self.assertEqual(2, len(skipped))

    def test_manual_force_all_still_includes_strong_confirmed_targets(self):
        user = {
            "username": "demo",
            "targets": ["confirmed", "pending"],
            "message_history": {
                "confirmed": {
                    "sentAt": self.now.isoformat(),
                    "status": "confirmed",
                    "confirmationLevel": "strong",
                    "needsVerification": False,
                }
            },
        }
        config = {"dailySendWindow": self.window}

        with patch.dict(os.environ, {"SPARKFLOW_MANUAL_RUN": "1"}, clear=False):
            prepared = tasks._prepare_active_users_for_run(config, [user])

        self.assertEqual(["confirmed", "pending"], prepared[0]["targets"])

    def test_message_choice_avoids_previous_and_last_when_possible(self):
        with patch.object(
            msg_builder,
            "build_message_candidates",
            return_value=["A", "B", "C"],
        ):
            selected = msg_builder.build_message(previous_message="A", last_message="B")

        self.assertEqual("C", selected)


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