"""Tests for AccountGuard: suspension, daily cap, threat detection, warmup."""

from __future__ import annotations

import json
from datetime import datetime, timedelta
from unittest.mock import patch

import pytest

from agent_samochodowy.account_guard import AccountGuard
from agent_samochodowy.config import Settings


def _settings(tmp_path, **overrides) -> Settings:
    defaults = dict(
        dgred_api_key="test",
        dry_run=True,
        db_path=str(tmp_path / "test.db"),
        billing_mode="shadow",
        max_daily_scans=30,
        warmup_until="",
        sendgrid_api_key="",
        notify_email_to="",
        notify_email_from="test@test.com",
        fb_cookies="",
        fb_proxy_url="",
    )
    defaults.update(overrides)
    return Settings(**defaults)


# ---------------------------------------------------------------------------
# Suspension
# ---------------------------------------------------------------------------

class TestSuspension:
    def test_not_suspended_by_default(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        assert guard.is_suspended() is False
        guard.close()

    def test_suspend_sets_flag(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        guard.suspend("test reason")
        assert guard.is_suspended() is True
        guard.close()

    def test_unsuspend_clears_flag(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        guard.suspend("test")
        guard.unsuspend()
        assert guard.is_suspended() is False
        guard.close()

    def test_suspension_persists_across_instances(self, tmp_path):
        s = _settings(tmp_path)
        g1 = AccountGuard(s)
        g1.suspend("test")
        g1.close()

        g2 = AccountGuard(s)
        assert g2.is_suspended() is True
        g2.close()


# ---------------------------------------------------------------------------
# Daily scan cap
# ---------------------------------------------------------------------------

class TestDailyCap:
    def test_under_cap_returns_true(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path, max_daily_scans=5))
        assert guard.check_daily_cap() is True
        guard.close()

    def test_at_cap_returns_false(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path, max_daily_scans=3))
        for _ in range(3):
            guard.increment_daily_count()
        assert guard.check_daily_cap() is False
        guard.close()

    def test_increment_returns_count(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        assert guard.increment_daily_count() == 1
        assert guard.increment_daily_count() == 2
        assert guard.daily_count() == 2
        guard.close()


# ---------------------------------------------------------------------------
# Threat detection in items
# ---------------------------------------------------------------------------

class TestThreatDetection:
    def test_clean_items_pass_through(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        items = [{"text": "Sprzedam turbinę"}, {"text": "Kupię alternator"}]
        result = guard.check_items("Test Group", items)
        assert len(result) == 2
        assert guard.is_suspended() is False
        guard.close()

    def test_error_items_filtered_out(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        items = [
            {"text": "Sprzedam turbinę"},
            {"url": "https://fb.com/groups/123", "error": "Some non-threat error"},
        ]
        result = guard.check_items("Test Group", items)
        assert len(result) == 1
        assert guard.is_suspended() is False
        guard.close()

    def test_private_group_error_suspends(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        items = [
            {"url": "https://fb.com/groups/123", "error": "PRIVATE GROUP and no data available"},
        ]
        result = guard.check_items("Test Group", items)
        assert len(result) == 0
        assert guard.is_suspended() is True
        guard.close()

    def test_checkpoint_error_suspends(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        items = [{"error": "Facebook requires you to verify your identity (checkpoint)"}]
        result = guard.check_items("Test Group", items)
        assert len(result) == 0
        assert guard.is_suspended() is True
        guard.close()

    def test_captcha_error_suspends(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        items = [{"error": "CAPTCHA required to continue"}]
        result = guard.check_items("Test Group", items)
        assert guard.is_suspended() is True
        guard.close()

    def test_blocked_error_suspends(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        items = [{"error": "Your account has been blocked"}]
        guard.check_items("Test Group", items)
        assert guard.is_suspended() is True
        guard.close()

    def test_login_error_suspends(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        items = [{"error": "You must log in to continue"}]
        guard.check_items("Test Group", items)
        assert guard.is_suspended() is True
        guard.close()


# ---------------------------------------------------------------------------
# Consecutive run failures
# ---------------------------------------------------------------------------

class TestRunFailures:
    def test_single_failure_no_suspend(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        guard.check_run_failure("FAILED")
        assert guard.is_suspended() is False
        guard.close()

    def test_three_consecutive_failures_suspends(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        guard.check_run_failure("FAILED")
        guard.check_run_failure("TIMED-OUT")
        guard.check_run_failure("ABORTED")
        assert guard.is_suspended() is True
        guard.close()

    def test_success_resets_counter(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        guard.check_run_failure("FAILED")
        guard.check_run_failure("FAILED")
        guard.check_run_failure("SUCCEEDED")  # reset
        guard.check_run_failure("FAILED")
        assert guard.is_suspended() is False
        guard.close()


# ---------------------------------------------------------------------------
# Zero-posts cycle detection
# ---------------------------------------------------------------------------

class TestZeroPostsCycles:
    def test_one_zero_cycle_no_suspend(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path))
        # Simulate: 3 groups, all 0 posts
        for _ in range(3):
            guard.record_group_result(0)
        guard.end_of_cycle_check()
        assert guard.is_suspended() is False
        guard.close()

    def test_two_zero_cycles_suspends(self, tmp_path):
        s = _settings(tmp_path)
        # Cycle 1
        g1 = AccountGuard(s)
        for _ in range(3):
            g1.record_group_result(0)
        g1.end_of_cycle_check()
        g1.close()

        # Cycle 2
        g2 = AccountGuard(s)
        for _ in range(3):
            g2.record_group_result(0)
        g2.end_of_cycle_check()
        assert g2.is_suspended() is True
        g2.close()

    def test_mixed_results_reset_counter(self, tmp_path):
        s = _settings(tmp_path)
        # Cycle 1: all zero
        g1 = AccountGuard(s)
        for _ in range(3):
            g1.record_group_result(0)
        g1.end_of_cycle_check()
        g1.close()

        # Cycle 2: some posts
        g2 = AccountGuard(s)
        g2.record_group_result(5)
        g2.record_group_result(0)
        g2.end_of_cycle_check()
        g2.close()

        # Cycle 3: all zero again — counter was reset, so no suspend
        g3 = AccountGuard(s)
        for _ in range(3):
            g3.record_group_result(0)
        g3.end_of_cycle_check()
        assert g3.is_suspended() is False
        g3.close()


# ---------------------------------------------------------------------------
# Warmup
# ---------------------------------------------------------------------------

class TestWarmup:
    def test_no_warmup_returns_none(self, tmp_path):
        guard = AccountGuard(_settings(tmp_path, warmup_until=""))
        assert guard.warmup_group_count() is None
        guard.close()

    def test_expired_warmup_returns_none(self, tmp_path):
        yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
        guard = AccountGuard(_settings(tmp_path, warmup_until=yesterday))
        assert guard.warmup_group_count() is None
        guard.close()

    def test_early_warmup_returns_1(self, tmp_path):
        # 13 days from now = day 1 of 14-day warmup
        future = (datetime.now() + timedelta(days=13)).strftime("%Y-%m-%d")
        guard = AccountGuard(_settings(tmp_path, warmup_until=future))
        assert guard.warmup_group_count() == 1
        guard.close()

    def test_mid_warmup_returns_2(self, tmp_path):
        # 9 days from now = day 5 of 14-day warmup
        future = (datetime.now() + timedelta(days=9)).strftime("%Y-%m-%d")
        guard = AccountGuard(_settings(tmp_path, warmup_until=future))
        assert guard.warmup_group_count() == 2
        guard.close()

    def test_late_warmup_returns_4(self, tmp_path):
        # 2 days from now = day 12 of 14-day warmup
        future = (datetime.now() + timedelta(days=2)).strftime("%Y-%m-%d")
        guard = AccountGuard(_settings(tmp_path, warmup_until=future))
        assert guard.warmup_group_count() == 4
        guard.close()


# ---------------------------------------------------------------------------
# Email alarm (mocked)
# ---------------------------------------------------------------------------

class TestAlarmEmail:
    @patch("sendgrid.SendGridAPIClient")
    def test_alarm_sends_email(self, MockSG, tmp_path):
        guard = AccountGuard(_settings(
            tmp_path,
            sendgrid_api_key="SG.fake",
            notify_email_to="test@test.com",
        ))
        guard.suspend("test threat")
        MockSG.assert_called_once_with("SG.fake")
        guard.close()

    def test_alarm_without_sendgrid_logs_warning(self, tmp_path, caplog):
        import logging
        guard = AccountGuard(_settings(tmp_path))
        with caplog.at_level(logging.WARNING):
            guard.suspend("test threat")
        assert any("Cannot send alarm" in r.message for r in caplog.records)
        guard.close()
