# 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 calendar registry (familiar/skills/calendar/accounts.py).
"""

import os
import sys
from pathlib import Path

import pytest

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

from familiar.skills.calendar.accounts import (
    _load_accounts_file,
    _migrate_caldav_env,
    _migrate_google_token,
    _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 / "calendar_accounts.json"
    monkeypatch.setattr(
        "familiar.skills.calendar.accounts._accounts_file",
        lambda: accounts_path,
    )
    # Also redirect _data_dir so migration checks use tmp_path
    monkeypatch.setattr(
        "familiar.skills.calendar.accounts._data_dir",
        lambda: tmp_path,
    )
    # Clear calendar env vars
    for key in [
        "CALDAV_URL",
        "CALDAV_USER",
        "CALDAV_PASSWORD",
        "CALENDAR_PROVIDER",
    ]:
        monkeypatch.delenv(key, raising=False)
    return accounts_path


GOOGLE_ACCOUNT = {
    "id": "google",
    "type": "google",
    "label": "Personal",
    "token_file": "google_token.json",
    "calendar_id": "primary",
}

CALDAV_ACCOUNT = {
    "id": "caldav",
    "type": "caldav",
    "label": "Work",
    "url": "https://cloud.example.org/remote.php/dav/calendars/alice/",
    "user": "alice",
    "password": "app-password",
}


# ── 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": [GOOGLE_ACCOUNT], "primary": "google"}
        _save_accounts_file(data)
        loaded = _load_accounts_file()
        assert loaded["accounts"][0]["type"] == "google"
        assert loaded["primary"] == "google"
        # 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(GOOGLE_ACCOUNT.copy())
        data = _load_accounts_file()
        assert len(data["accounts"]) == 1
        assert data["primary"] == "google"

    def test_add_second_preserves_primary(self):
        add_account(GOOGLE_ACCOUNT.copy())
        add_account(CALDAV_ACCOUNT.copy())
        data = _load_accounts_file()
        assert len(data["accounts"]) == 2
        assert data["primary"] == "google"

    def test_upsert_existing(self):
        add_account(CALDAV_ACCOUNT.copy())
        updated = {**CALDAV_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_caldav_primary_to_env(self):
        add_account(CALDAV_ACCOUNT.copy())
        assert os.environ["CALDAV_URL"] == CALDAV_ACCOUNT["url"]
        assert os.environ["CALDAV_USER"] == "alice"
        assert os.environ["CALDAV_PASSWORD"] == "app-password"
        assert os.environ["CALENDAR_PROVIDER"] == "caldav"

    def test_syncs_google_primary_to_env(self):
        add_account(GOOGLE_ACCOUNT.copy())
        assert os.environ["CALENDAR_PROVIDER"] == "google"

    def test_second_account_does_not_overwrite_env(self):
        add_account(GOOGLE_ACCOUNT.copy())
        add_account(CALDAV_ACCOUNT.copy())
        # Primary is still google, so env should still be google
        assert os.environ["CALENDAR_PROVIDER"] == "google"


class TestRemoveAccount:
    def test_remove_existing(self):
        add_account(GOOGLE_ACCOUNT.copy())
        add_account(CALDAV_ACCOUNT.copy())
        assert remove_account("google") is True
        data = _load_accounts_file()
        assert len(data["accounts"]) == 1
        assert data["primary"] == "caldav"

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

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


class TestGetAccount:
    def test_none_returns_primary(self):
        add_account(GOOGLE_ACCOUNT.copy())
        add_account(CALDAV_ACCOUNT.copy())
        acct = get_account(None)
        assert acct["id"] == "google"

    def test_by_id(self):
        add_account(GOOGLE_ACCOUNT.copy())
        add_account(CALDAV_ACCOUNT.copy())
        acct = get_account("caldav")
        assert acct["type"] == "caldav"

    def test_by_type(self):
        acct_data = {**GOOGLE_ACCOUNT, "id": "google-2"}
        add_account(GOOGLE_ACCOUNT.copy())
        add_account(acct_data)
        # "google" matches the first account by id, but type lookup also works
        acct = get_account("google")
        assert acct["id"] == "google"

    def test_by_label_partial(self):
        add_account(GOOGLE_ACCOUNT.copy())
        add_account(CALDAV_ACCOUNT.copy())
        acct = get_account("work")
        assert acct["id"] == "caldav"

    def test_case_insensitive(self):
        add_account(GOOGLE_ACCOUNT.copy())
        assert get_account("Google") is not None
        assert get_account("GOOGLE") is not None

    def test_label_case_insensitive(self):
        add_account(CALDAV_ACCOUNT.copy())
        assert get_account("Work") is not None
        assert get_account("WORK") is not None

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


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

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

    def test_migrates_from_google_token(self, tmp_path):
        token = tmp_path / "google_token.json"
        token.write_text("{}")
        accounts = get_all_accounts()
        assert len(accounts) == 1
        assert accounts[0]["type"] == "google"
        assert accounts[0]["token_file"] == "google_token.json"

    def test_migrates_from_caldav_env(self, monkeypatch):
        monkeypatch.setenv("CALDAV_URL", "https://cloud.example.org/dav/")
        monkeypatch.setenv("CALDAV_USER", "alice")
        monkeypatch.setenv("CALDAV_PASSWORD", "secret")
        accounts = get_all_accounts()
        assert len(accounts) == 1
        assert accounts[0]["type"] == "caldav"
        assert accounts[0]["url"] == "https://cloud.example.org/dav/"

    def test_migrates_both(self, tmp_path, monkeypatch):
        token = tmp_path / "google_token.json"
        token.write_text("{}")
        monkeypatch.setenv("CALDAV_URL", "https://cloud.example.org/dav/")
        monkeypatch.setenv("CALDAV_USER", "alice")
        monkeypatch.setenv("CALDAV_PASSWORD", "secret")
        accounts = get_all_accounts()
        assert len(accounts) == 2
        assert accounts[0]["type"] == "google"
        assert accounts[1]["type"] == "caldav"

    def test_no_migration_if_registry_exists(self, tmp_path, monkeypatch):
        add_account(CALDAV_ACCOUNT.copy())
        # Even with a google token file present, registry takes precedence
        token = tmp_path / "google_token.json"
        token.write_text("{}")
        accounts = get_all_accounts()
        assert len(accounts) == 1
        assert accounts[0]["type"] == "caldav"


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


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


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

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

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

    def test_caldav(self):
        assert _unique_id("caldav", []) == "caldav"

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


class TestMigration:
    def test_no_google_token(self, tmp_path):
        # No token file exists — should return None
        assert _migrate_google_token() is None

    def test_google_token_exists(self, tmp_path):
        token = tmp_path / "google_token.json"
        token.write_text("{}")
        result = _migrate_google_token()
        assert result is not None
        assert result["type"] == "google"
        assert result["id"] == "google"
        assert result["token_file"] == "google_token.json"

    def test_no_caldav_env(self):
        assert _migrate_caldav_env() is None

    def test_caldav_env_partial(self, monkeypatch):
        monkeypatch.setenv("CALDAV_URL", "https://example.org/dav/")
        # Missing user and password
        assert _migrate_caldav_env() is None

    def test_caldav_env_complete(self, monkeypatch):
        monkeypatch.setenv("CALDAV_URL", "https://example.org/dav/")
        monkeypatch.setenv("CALDAV_USER", "alice")
        monkeypatch.setenv("CALDAV_PASSWORD", "secret")
        result = _migrate_caldav_env()
        assert result is not None
        assert result["type"] == "caldav"
        assert result["id"] == "caldav"
        assert result["url"] == "https://example.org/dav/"


class TestSyncPrimaryToEnv:
    def test_sync_caldav(self):
        _sync_primary_to_env(CALDAV_ACCOUNT)
        assert os.environ["CALDAV_URL"] == CALDAV_ACCOUNT["url"]
        assert os.environ["CALDAV_USER"] == "alice"
        assert os.environ["CALDAV_PASSWORD"] == "app-password"
        assert os.environ["CALENDAR_PROVIDER"] == "caldav"

    def test_sync_google(self):
        _sync_primary_to_env(GOOGLE_ACCOUNT)
        assert os.environ["CALENDAR_PROVIDER"] == "google"
