# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Unit tests for multi-account email registry (familiar/skills/email/accounts.py)
and multi-account dispatch in email skill.
"""

import os
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))

from familiar.skills.email.accounts import (
    _detect_provider,
    _load_accounts_file,
    _migrate_env_vars,
    _save_accounts_file,
    _sync_primary_to_env,
    _unique_id,
    account_count,
    add_account,
    get_account,
    get_all_accounts,
    remove_account,
)


@pytest.fixture(autouse=True)
def isolated_accounts(tmp_path, monkeypatch):
    """Redirect accounts file to a temp directory and clean env vars."""
    accounts_path = tmp_path / "email_accounts.json"
    monkeypatch.setattr(
        "familiar.skills.email.accounts._accounts_file",
        lambda: accounts_path,
    )
    # Clear email env vars
    for key in [
        "EMAIL_ADDRESS",
        "EMAIL_PASSWORD",
        "EMAIL_IMAP_SERVER",
        "EMAIL_SMTP_SERVER",
        "EMAIL_IMAP_PORT",
        "EMAIL_SMTP_PORT",
    ]:
        monkeypatch.delenv(key, raising=False)
    return accounts_path


GMAIL_ACCOUNT = {
    "id": "gmail",
    "address": "user@gmail.com",
    "password": "app-pass-1",
    "imap_server": "imap.gmail.com",
    "smtp_server": "smtp.gmail.com",
    "imap_port": 993,
    "smtp_port": 587,
    "provider": "gmail",
}

YAHOO_ACCOUNT = {
    "id": "yahoo",
    "address": "user@yahoo.com",
    "password": "app-pass-2",
    "imap_server": "imap.mail.yahoo.com",
    "smtp_server": "smtp.mail.yahoo.com",
    "imap_port": 993,
    "smtp_port": 587,
    "provider": "yahoo",
}


# ── Basic registry CRUD ─────────────────────────────────────────────


class TestLoadSave:
    def test_empty_load(self):
        data = _load_accounts_file()
        assert data == {"accounts": [], "primary": None}

    def test_save_and_load(self, isolated_accounts):
        data = {"accounts": [GMAIL_ACCOUNT], "primary": "gmail"}
        _save_accounts_file(data)
        loaded = _load_accounts_file()
        assert loaded["accounts"][0]["address"] == "user@gmail.com"
        assert loaded["primary"] == "gmail"
        # File permissions should be 600
        stat = isolated_accounts.stat()
        assert oct(stat.st_mode)[-3:] == "600"

    def test_corrupted_json(self, isolated_accounts):
        isolated_accounts.write_text("not json")
        data = _load_accounts_file()
        assert data == {"accounts": [], "primary": None}


class TestAddAccount:
    def test_add_first_sets_primary(self):
        add_account(GMAIL_ACCOUNT.copy())
        data = _load_accounts_file()
        assert len(data["accounts"]) == 1
        assert data["primary"] == "gmail"

    def test_add_second_preserves_primary(self):
        add_account(GMAIL_ACCOUNT.copy())
        add_account(YAHOO_ACCOUNT.copy())
        data = _load_accounts_file()
        assert len(data["accounts"]) == 2
        assert data["primary"] == "gmail"

    def test_upsert_existing(self):
        add_account(GMAIL_ACCOUNT.copy())
        updated = {**GMAIL_ACCOUNT, "password": "new-pass"}
        add_account(updated)
        data = _load_accounts_file()
        assert len(data["accounts"]) == 1
        assert data["accounts"][0]["password"] == "new-pass"

    def test_syncs_primary_to_env(self):
        add_account(GMAIL_ACCOUNT.copy())
        assert os.environ["EMAIL_ADDRESS"] == "user@gmail.com"
        assert os.environ["EMAIL_PASSWORD"] == "app-pass-1"
        assert os.environ["EMAIL_IMAP_SERVER"] == "imap.gmail.com"

    def test_second_account_does_not_overwrite_env(self):
        add_account(GMAIL_ACCOUNT.copy())
        add_account(YAHOO_ACCOUNT.copy())
        # Primary is still gmail, so env should still be gmail
        assert os.environ["EMAIL_ADDRESS"] == "user@gmail.com"


class TestRemoveAccount:
    def test_remove_existing(self):
        add_account(GMAIL_ACCOUNT.copy())
        add_account(YAHOO_ACCOUNT.copy())
        assert remove_account("gmail") is True
        data = _load_accounts_file()
        assert len(data["accounts"]) == 1
        assert data["primary"] == "yahoo"

    def test_remove_nonexistent(self):
        add_account(GMAIL_ACCOUNT.copy())
        assert remove_account("nonexistent") is False

    def test_remove_last_account(self):
        add_account(GMAIL_ACCOUNT.copy())
        remove_account("gmail")
        data = _load_accounts_file()
        assert data["accounts"] == []
        assert data["primary"] is None


class TestGetAccount:
    def test_none_returns_primary(self):
        add_account(GMAIL_ACCOUNT.copy())
        add_account(YAHOO_ACCOUNT.copy())
        acct = get_account(None)
        assert acct["id"] == "gmail"

    def test_by_id(self):
        add_account(GMAIL_ACCOUNT.copy())
        add_account(YAHOO_ACCOUNT.copy())
        acct = get_account("yahoo")
        assert acct["address"] == "user@yahoo.com"

    def test_by_provider(self):
        acct_data = {**GMAIL_ACCOUNT, "id": "gmail-2"}
        add_account(GMAIL_ACCOUNT.copy())
        add_account(acct_data)
        # "gmail" matches the first account by provider
        acct = get_account("gmail")
        assert acct["id"] == "gmail"

    def test_by_partial_address(self):
        add_account(GMAIL_ACCOUNT.copy())
        acct = get_account("user@gmail")
        assert acct["id"] == "gmail"

    def test_not_found(self):
        add_account(GMAIL_ACCOUNT.copy())
        assert get_account("outlook") is None

    def test_case_insensitive(self):
        add_account(GMAIL_ACCOUNT.copy())
        assert get_account("Gmail") is not None
        assert get_account("GMAIL") is not None


class TestGetAllAccounts:
    def test_returns_all(self):
        add_account(GMAIL_ACCOUNT.copy())
        add_account(YAHOO_ACCOUNT.copy())
        accounts = get_all_accounts()
        assert len(accounts) == 2

    def test_empty(self):
        assert get_all_accounts() == []

    def test_migrates_from_env(self, monkeypatch):
        monkeypatch.setenv("EMAIL_ADDRESS", "env@gmail.com")
        monkeypatch.setenv("EMAIL_PASSWORD", "env-pass")
        monkeypatch.setenv("EMAIL_IMAP_SERVER", "imap.gmail.com")
        accounts = get_all_accounts()
        assert len(accounts) == 1
        assert accounts[0]["address"] == "env@gmail.com"
        assert accounts[0]["provider"] == "gmail"

    def test_no_migration_if_registry_exists(self, monkeypatch):
        add_account(YAHOO_ACCOUNT.copy())
        monkeypatch.setenv("EMAIL_ADDRESS", "env@gmail.com")
        monkeypatch.setenv("EMAIL_PASSWORD", "env-pass")
        accounts = get_all_accounts()
        assert len(accounts) == 1
        assert accounts[0]["provider"] == "yahoo"


class TestAccountCount:
    def test_count(self):
        assert account_count() == 0
        add_account(GMAIL_ACCOUNT.copy())
        assert account_count() == 1
        add_account(YAHOO_ACCOUNT.copy())
        assert account_count() == 2


# ── Helper functions ─────────────────────────────────────────────────


class TestDetectProvider:
    def test_gmail(self):
        assert _detect_provider("imap.gmail.com") == "gmail"

    def test_yahoo(self):
        assert _detect_provider("imap.mail.yahoo.com") == "yahoo"

    def test_outlook(self):
        assert _detect_provider("outlook.office365.com") == "outlook"

    def test_proton(self):
        assert _detect_provider("127.0.0.1") == "proton"

    def test_unknown(self):
        assert _detect_provider("mail.example.com") == "custom"


class TestUniqueId:
    def test_first_account(self):
        assert _unique_id("gmail", []) == "gmail"

    def test_second_gmail(self):
        accounts = [{"id": "gmail"}]
        assert _unique_id("gmail", accounts) == "gmail-2"

    def test_third_gmail(self):
        accounts = [{"id": "gmail"}, {"id": "gmail-2"}]
        assert _unique_id("gmail", accounts) == "gmail-3"


class TestMigrateEnvVars:
    def test_no_env(self):
        assert _migrate_env_vars() is None

    def test_with_env(self, monkeypatch):
        monkeypatch.setenv("EMAIL_ADDRESS", "test@yahoo.com")
        monkeypatch.setenv("EMAIL_PASSWORD", "pass")
        monkeypatch.setenv("EMAIL_IMAP_SERVER", "imap.mail.yahoo.com")
        result = _migrate_env_vars()
        assert result is not None
        assert result["provider"] == "yahoo"
        assert result["id"] == "yahoo"


class TestSyncPrimaryToEnv:
    def test_sync(self):
        _sync_primary_to_env(GMAIL_ACCOUNT)
        assert os.environ["EMAIL_ADDRESS"] == "user@gmail.com"
        assert os.environ["EMAIL_PASSWORD"] == "app-pass-1"
        assert os.environ["EMAIL_IMAP_SERVER"] == "imap.gmail.com"
        assert os.environ["EMAIL_SMTP_SERVER"] == "smtp.gmail.com"
        assert os.environ["EMAIL_IMAP_PORT"] == "993"
        assert os.environ["EMAIL_SMTP_PORT"] == "587"


# ── Multi-account dispatch (email skill integration) ─────────────────


class TestImapConfigRouting:
    """Test that _imap_config routes to the correct account."""

    def test_default_returns_primary(self):
        add_account(GMAIL_ACCOUNT.copy())
        add_account(YAHOO_ACCOUNT.copy())
        from familiar.skills.email.skill import _imap_config

        cfg = _imap_config()
        assert cfg["address"] == "user@gmail.com"

    def test_named_account(self):
        add_account(GMAIL_ACCOUNT.copy())
        add_account(YAHOO_ACCOUNT.copy())
        from familiar.skills.email.skill import _imap_config

        cfg = _imap_config("yahoo")
        assert cfg["address"] == "user@yahoo.com"

    def test_legacy_fallback(self, monkeypatch):
        """When no accounts and no env vars, returns empty."""
        from familiar.skills.email.skill import _imap_config

        cfg = _imap_config()
        assert cfg["address"] == ""
