"""Tests for the generic ChannelSessionStore."""

import json
from datetime import datetime, timezone, timedelta
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from fliiq.runtime.channel_store import ChannelSessionStore, _safe_filename


# ── Filename sanitization ────────────────────────────────────────────


def test_safe_filename_email():
    assert _safe_filename("alice@example.com") == "alice_example_com"


def test_safe_filename_phone():
    assert _safe_filename("+1234567890") == "+1234567890"


def test_safe_filename_chat_id():
    assert _safe_filename("42") == "42"


# ── Telegram channel ─────────────────────────────────────────────────


def test_telegram_log_creates_file(tmp_path):
    store = ChannelSessionStore(tmp_path, "telegram")
    store.log_message("42", "user", "hello")
    path = tmp_path / "telegram_sessions" / "42.jsonl"
    assert path.exists()
    entry = json.loads(path.read_text().strip())
    assert entry["role"] == "user"
    assert entry["text"] == "hello"


def test_telegram_thread_support(tmp_path):
    store = ChannelSessionStore(tmp_path, "telegram")
    store.log_message("42", "user", "msg", thread_id=101)
    path = tmp_path / "telegram_sessions" / "42_thread_101.jsonl"
    assert path.exists()
    msgs = store.load_recent("42", thread_id=101)
    assert 'message_thread_id="101"' in msgs[0]["content"]


def test_telegram_tag_format(tmp_path):
    store = ChannelSessionStore(tmp_path, "telegram")
    store.log_message("42", "user", "hi")
    msgs = store.load_recent("42")
    assert '<external_message source="telegram" chat_id="42">' in msgs[0]["content"]


# ── Email channel ────────────────────────────────────────────────────


def test_email_log_creates_file(tmp_path):
    store = ChannelSessionStore(tmp_path, "email")
    store.log_message("alice@example.com", "user", "hello")
    path = tmp_path / "email_sessions" / "alice_example_com.jsonl"
    assert path.exists()


def test_email_tag_format(tmp_path):
    store = ChannelSessionStore(tmp_path, "email")
    store.log_message("alice@example.com", "user", "hi")
    msgs = store.load_recent("alice@example.com")
    assert '<external_message source="email" sender="alice@example.com">' in msgs[0]["content"]


def test_email_roundtrip(tmp_path):
    store = ChannelSessionStore(tmp_path, "email")
    store.log_message("bob@test.com", "user", "question?")
    store.log_message("bob@test.com", "assistant", "answer!")
    msgs = store.load_recent("bob@test.com")
    assert len(msgs) == 2
    assert msgs[0]["role"] == "user"
    assert msgs[1]["role"] == "assistant"
    assert msgs[1]["content"] == "answer!"


# ── SMS channel ──────────────────────────────────────────────────────


def test_sms_log_creates_file(tmp_path):
    store = ChannelSessionStore(tmp_path, "sms")
    store.log_message("+1234567890", "assistant", "reminder")
    path = tmp_path / "sms_sessions" / "+1234567890.jsonl"
    assert path.exists()


def test_sms_tag_format(tmp_path):
    store = ChannelSessionStore(tmp_path, "sms")
    store.log_message("+1234567890", "user", "hi")
    msgs = store.load_recent("+1234567890")
    assert '<external_message source="sms" phone="+1234567890">' in msgs[0]["content"]


# ── Cross-channel isolation ──────────────────────────────────────────


def test_channels_use_separate_dirs(tmp_path):
    t = ChannelSessionStore(tmp_path, "telegram")
    e = ChannelSessionStore(tmp_path, "email")
    s = ChannelSessionStore(tmp_path, "sms")
    t.log_message("42", "user", "t")
    e.log_message("a@b.com", "user", "e")
    s.log_message("+1", "user", "s")
    assert (tmp_path / "telegram_sessions").is_dir()
    assert (tmp_path / "email_sessions").is_dir()
    assert (tmp_path / "sms_sessions").is_dir()


# ── Generic behavior (max messages, age filter, malformed lines) ─────


def test_max_messages_limit(tmp_path):
    store = ChannelSessionStore(tmp_path, "email")
    for i in range(30):
        store.log_message("a@b.com", "user", f"msg {i}")
    msgs = store.load_recent("a@b.com", max_messages=10)
    assert len(msgs) == 10
    assert "msg 20" in msgs[0]["content"]
    assert "msg 29" in msgs[9]["content"]


def test_age_filter(tmp_path):
    store = ChannelSessionStore(tmp_path, "sms")
    path = tmp_path / "sms_sessions" / "+1.jsonl"
    path.parent.mkdir(parents=True, exist_ok=True)

    old_ts = (datetime.now(timezone.utc) - timedelta(hours=48)).isoformat()
    recent_ts = datetime.now(timezone.utc).isoformat()
    path.write_text(
        json.dumps({"role": "user", "text": "old", "ts": old_ts}) + "\n"
        + json.dumps({"role": "user", "text": "new", "ts": recent_ts}) + "\n"
    )
    msgs = store.load_recent("+1", max_age_hours=24)
    assert len(msgs) == 1
    assert "new" in msgs[0]["content"]


def test_malformed_lines_skipped(tmp_path):
    store = ChannelSessionStore(tmp_path, "telegram")
    path = tmp_path / "telegram_sessions" / "42.jsonl"
    path.parent.mkdir(parents=True, exist_ok=True)
    ts = datetime.now(timezone.utc).isoformat()
    path.write_text(
        f"not json\n"
        f'{{"role": "user", "text": "good", "ts": "{ts}"}}\n'
        f'{{"role": "user", "missing_ts": true}}\n'
    )
    msgs = store.load_recent("42")
    assert len(msgs) == 1
    assert "good" in msgs[0]["content"]


def test_empty_log_returns_empty(tmp_path):
    store = ChannelSessionStore(tmp_path, "email")
    assert store.load_recent("nobody@test.com") == []


def test_assistant_messages_not_tagged(tmp_path):
    store = ChannelSessionStore(tmp_path, "email")
    store.log_message("a@b.com", "assistant", "reply")
    msgs = store.load_recent("a@b.com")
    assert msgs[0]["content"] == "reply"
    assert "<external_message" not in msgs[0]["content"]


# ── Unknown channel fallback ─────────────────────────────────────────


def test_unknown_channel_tag(tmp_path):
    store = ChannelSessionStore(tmp_path, "slack")
    store.log_message("C12345", "user", "hi")
    msgs = store.load_recent("C12345")
    assert '<external_message source="slack" id="C12345">' in msgs[0]["content"]


# ── Integration: send_email logs outbound ────────────────────────────


async def test_send_email_logs_outbound(tmp_path, monkeypatch):
    """send_email handler logs outbound message to email session store."""
    monkeypatch.setattr("fliiq.runtime.config.global_fliiq_dir", lambda: tmp_path)

    with patch(
        "fliiq.data.skills.core.send_email.main.resolve_gmail_creds",
        new_callable=AsyncMock,
        return_value=("bot@gmail.com", "password", "fake-pass"),
    ), patch(
        "fliiq.data.skills.core.send_email.main._send_smtp",
    ):
        from fliiq.data.skills.core.send_email.main import handler
        result = await handler({
            "to": "alice@example.com",
            "subject": "Test",
            "body": "Hello Alice",
        })

    assert "Email sent" in result["status"]
    store = ChannelSessionStore(tmp_path, "email")
    msgs = store.load_recent("alice@example.com")
    assert len(msgs) == 1
    assert msgs[0]["role"] == "assistant"
    assert msgs[0]["content"] == "Hello Alice"


# ── Integration: send_sms logs outbound ──────────────────────────────


async def test_send_sms_logs_outbound(tmp_path, monkeypatch):
    """send_sms handler logs outbound message to sms session store."""
    monkeypatch.setenv("TWILIO_ACCOUNT_SID", "fake-sid")
    monkeypatch.setenv("TWILIO_AUTH_TOKEN", "fake-token")
    monkeypatch.setenv("TWILIO_PHONE_NUMBER", "+10000000000")
    monkeypatch.setattr("fliiq.runtime.config.global_fliiq_dir", lambda: tmp_path)

    mock_response = MagicMock()
    mock_response.status_code = 201
    mock_response.json.return_value = {"sid": "SM123"}

    with patch("httpx.AsyncClient") as mock_client_cls:
        mock_client = AsyncMock()
        mock_client.post.return_value = mock_response
        mock_client_cls.return_value.__aenter__ = AsyncMock(return_value=mock_client)
        mock_client_cls.return_value.__aexit__ = AsyncMock(return_value=False)

        from fliiq.data.skills.core.send_sms.main import handler
        result = await handler({"to": "+1555123456", "body": "Reminder: call Sarah"})

    assert result["sid"] == "SM123"
    store = ChannelSessionStore(tmp_path, "sms")
    msgs = store.load_recent("+1555123456")
    assert len(msgs) == 1
    assert msgs[0]["content"] == "Reminder: call Sarah"


# ── Integration: email listener seeds from store ─────────────────────


async def test_email_listener_seeds_from_store(tmp_path):
    """New email session loads recent messages from persistent store."""
    store = ChannelSessionStore(tmp_path, "email")
    store.log_message("alice@example.com", "assistant", "Hi Alice, just checking in!")

    from fliiq.runtime.email.listener import EmailListener

    listener = EmailListener(MagicMock(), tmp_path, fliiq_dir=tmp_path)

    with patch(
        "fliiq.runtime.email.listener.setup_agent_resources",
        new_callable=AsyncMock,
    ) as mock_setup:
        mock_setup.return_value = MagicMock()
        session = await listener._get_or_create_session("alice@example.com")

    messages = session["messages"]
    assert len(messages) == 1
    assert messages[0]["role"] == "assistant"
    assert messages[0]["content"] == "Hi Alice, just checking in!"


async def test_email_listener_logs_inbound(tmp_path):
    """_handle_email logs inbound user message to store."""
    from fliiq.runtime.email.listener import EmailListener

    listener = EmailListener(MagicMock(), tmp_path, fliiq_dir=tmp_path)

    mock_resources = MagicMock()
    mock_resources.soul = ""
    mock_resources.user_soul = None
    mock_resources.skill_info = []
    mock_resources.memory_context = None
    mock_resources.user_profile = None
    mock_resources.fliiq_email = None
    mock_resources.llm = MagicMock()
    mock_resources.tools = MagicMock()

    mock_result = MagicMock()
    mock_result.messages = []
    mock_result.iterations = 0

    with patch.object(
        listener, "_get_or_create_session",
        new_callable=AsyncMock,
        return_value={"messages": [], "resources": mock_resources},
    ), patch(
        "fliiq.runtime.email.listener.agent_loop",
        new_callable=AsyncMock,
        return_value=mock_result,
    ), patch(
        "fliiq.runtime.email.listener.assemble_agent_prompt",
        return_value="system prompt",
    ), patch(
        "fliiq.runtime.agent.setup.enrich_memory_context",
        return_value=None,
    ):
        email_data = {
            "id": "1",
            "from": "Alice <alice@example.com>",
            "to": "bot@gmail.com",
            "cc": "",
            "subject": "Help",
            "date": "Mon, 1 Mar 2026 12:00:00 +0000",
            "body": "I need help with something",
            "message_id": "<msg-1@mail.gmail.com>",
            "in_reply_to": "",
            "references": "",
        }
        await listener._handle_email(email_data)

    store = ChannelSessionStore(tmp_path, "email")
    msgs = store.load_recent("alice@example.com")
    assert len(msgs) == 1
    assert msgs[0]["role"] == "user"
    assert "I need help with something" in msgs[0]["content"]
