# retoor <retoor@molodetz.nl>

import dataclasses
import json

import pytest

from devplacepy import database
from devplacepy.services.devii.config import load_settings
from devplacepy.services.devii.email.controller import EmailController
from devplacepy.services.devii.errors import AuthRequiredError, ToolInputError
from devplacepy.utils import generate_uid

from tests.conftest import run_async


def _settings(**overrides):
    base = load_settings()
    if overrides:
        return dataclasses.replace(base, **overrides)
    return base


def _owner():
    return f"email-owner-{generate_uid()[-12:]}"


def test_guest_is_denied(local_db):
    controller = EmailController(_settings(), owner_kind="guest", owner_id="g1")
    with pytest.raises(AuthRequiredError):
        run_async(controller.dispatch("email_accounts_list", {}))


def test_disabled_flag_blocks_user(local_db):
    controller = EmailController(
        _settings(email_enabled=False), owner_kind="user", owner_id="u1"
    )
    with pytest.raises(ToolInputError):
        run_async(controller.dispatch("email_accounts_list", {}))


def test_unknown_tool_raises(local_db):
    controller = EmailController(_settings(), owner_kind="user", owner_id=_owner())
    with pytest.raises(ToolInputError):
        run_async(controller.dispatch("email_no_such_tool", {}))


def test_account_set_masks_password(local_db):
    owner = _owner()
    controller = EmailController(_settings(), owner_kind="user", owner_id=owner)
    out = run_async(
        controller.dispatch(
            "email_account_set",
            {
                "account": "work",
                "imap_host": "mail.example.com",
                "smtp_host": "smtp.example.com",
                "username": "me@example.com",
                "password": "topsecret",
            },
        )
    )
    account = json.loads(out)["account"]
    assert "password" not in account
    assert account["password_set"] is True
    assert account["username"] == "me@example.com"


def test_account_set_applies_default_ports(local_db):
    owner = _owner()
    controller = EmailController(_settings(), owner_kind="user", owner_id=owner)
    out = run_async(
        controller.dispatch(
            "email_account_set",
            {"account": "work", "imap_host": "mail.example.com"},
        )
    )
    account = json.loads(out)["account"]
    assert account["imap_port"] == 993
    assert account["smtp_port"] == 587


def test_account_set_coerces_booleans_and_ports(local_db):
    owner = _owner()
    controller = EmailController(_settings(), owner_kind="user", owner_id=owner)
    out = run_async(
        controller.dispatch(
            "email_account_set",
            {
                "account": "work",
                "imap_ssl": "true",
                "smtp_starttls": "0",
                "imap_port": "1993",
            },
        )
    )
    account = json.loads(out)["account"]
    assert account["imap_ssl"] is True
    assert account["smtp_starttls"] is False
    assert account["imap_port"] == 1993


def test_account_set_rejects_non_integer_port(local_db):
    owner = _owner()
    controller = EmailController(_settings(), owner_kind="user", owner_id=owner)
    with pytest.raises(ToolInputError):
        run_async(
            controller.dispatch(
                "email_account_set",
                {"account": "work", "imap_port": "not-a-port"},
            )
        )


def test_account_get_unknown_label_raises(local_db):
    controller = EmailController(_settings(), owner_kind="user", owner_id=_owner())
    with pytest.raises(ToolInputError):
        run_async(controller.dispatch("email_account_get", {"account": "missing"}))


def test_account_requires_label(local_db):
    controller = EmailController(_settings(), owner_kind="user", owner_id=_owner())
    with pytest.raises(ToolInputError):
        run_async(controller.dispatch("email_account_get", {}))


def test_account_delete_soft_deletes(local_db):
    owner = _owner()
    controller = EmailController(_settings(), owner_kind="user", owner_id=owner)
    run_async(
        controller.dispatch(
            "email_account_set",
            {"account": "work", "imap_host": "mail.example.com"},
        )
    )
    out = run_async(controller.dispatch("email_account_delete", {"account": "work"}))
    assert json.loads(out)["removed"] == 1
    assert database.get_email_account("user", owner, "work") is None


def test_accounts_list_excludes_deleted(local_db):
    owner = _owner()
    controller = EmailController(_settings(), owner_kind="user", owner_id=owner)
    run_async(
        controller.dispatch(
            "email_account_set", {"account": "keep", "imap_host": "a.example.com"}
        )
    )
    run_async(
        controller.dispatch(
            "email_account_set", {"account": "drop", "imap_host": "b.example.com"}
        )
    )
    run_async(controller.dispatch("email_account_delete", {"account": "drop"}))
    out = run_async(controller.dispatch("email_accounts_list", {}))
    labels = [account["label"] for account in json.loads(out)["accounts"]]
    assert "keep" in labels
    assert "drop" not in labels


def test_protocol_tool_without_account_raises(local_db):
    controller = EmailController(_settings(), owner_kind="user", owner_id=_owner())
    with pytest.raises(ToolInputError):
        run_async(controller.dispatch("email_list_folders", {"account": "absent"}))


def test_criteria_builds_imap_search_tokens(local_db):
    controller = EmailController(_settings(), owner_kind="user", owner_id=_owner())
    criteria = controller._criteria(
        {"unseen": True, "from": "alice@x.dev", "subject": "hi"}
    )
    assert "UNSEEN" in criteria
    assert "FROM" in criteria
    assert "SUBJECT" in criteria


def test_limit_is_clamped(local_db):
    controller = EmailController(_settings(), owner_kind="user", owner_id=_owner())
    assert controller._limit({"limit": 9999}) == 100
    assert controller._limit({"limit": 0}) == 1
    assert controller._limit({"limit": "bad"}) == 25


def test_mark_rejects_invalid_state(local_db):
    owner = _owner()
    controller = EmailController(_settings(), owner_kind="user", owner_id=owner)
    run_async(
        controller.dispatch(
            "email_account_set", {"account": "work", "imap_host": "mail.example.com"}
        )
    )
    with pytest.raises(ToolInputError):
        run_async(
            controller.dispatch(
                "email_mark", {"account": "work", "uid": "5", "state": "bogus"}
            )
        )


def test_send_requires_recipient(local_db):
    owner = _owner()
    controller = EmailController(_settings(), owner_kind="user", owner_id=owner)
    run_async(
        controller.dispatch(
            "email_account_set", {"account": "work", "smtp_host": "smtp.example.com"}
        )
    )
    with pytest.raises(ToolInputError):
        run_async(
            controller.dispatch(
                "email_send", {"account": "work", "subject": "hi", "body": "b"}
            )
        )
