import json
import urllib.error
from unittest.mock import MagicMock, mock_open, patch

import pytest

from claude_meter.api import FetchError, fetch_usage, get_access_token, get_extra_usage_enabled


class TestGetExtraUsageEnabled:
    @patch("claude_meter.api.Path")
    def test_returns_true_when_enabled(self, mock_path_cls):
        config = {"oauthAccount": {"hasExtraUsageEnabled": True}}
        mock_path_cls.home.return_value.__truediv__ = lambda self, other: self
        mock_config_path = mock_path_cls.home.return_value
        with patch("builtins.open", mock_open(read_data=json.dumps(config))):
            assert get_extra_usage_enabled() is True

    @patch("claude_meter.api.Path")
    def test_returns_false_when_disabled(self, mock_path_cls):
        config = {"oauthAccount": {"hasExtraUsageEnabled": False}}
        mock_path_cls.home.return_value.__truediv__ = lambda self, other: self
        mock_config_path = mock_path_cls.home.return_value
        with patch("builtins.open", mock_open(read_data=json.dumps(config))):
            assert get_extra_usage_enabled() is False

    @patch("claude_meter.api.Path")
    def test_returns_none_when_file_missing(self, mock_path_cls):
        mock_path_cls.home.return_value.__truediv__ = lambda self, other: self
        with patch("builtins.open", side_effect=FileNotFoundError):
            assert get_extra_usage_enabled() is None

    @patch("claude_meter.api.Path")
    def test_returns_none_when_field_absent(self, mock_path_cls):
        config = {"someOtherKey": {}}
        mock_path_cls.home.return_value.__truediv__ = lambda self, other: self
        with patch("builtins.open", mock_open(read_data=json.dumps(config))):
            assert get_extra_usage_enabled() is None


class TestGetAccessToken:
    @patch.dict("os.environ", {"CLAUDE_CODE_OAUTH_TOKEN": "env-token"})
    def test_from_env_var(self):
        assert get_access_token() == "env-token"

    @patch.dict("os.environ", {}, clear=True)
    @patch("claude_meter.api.Path")
    def test_from_credentials_file(self, mock_path_cls):
        creds = {"claudeAiOauth": {"accessToken": "file-token"}}
        mock_path_cls.home.return_value.__truediv__ = lambda *a: mock_path_cls.home.return_value
        mock_creds_path = mock_path_cls.home.return_value
        mock_creds_path.exists.return_value = True

        with patch("builtins.open", mock_open(read_data=json.dumps(creds))):
            assert get_access_token() == "file-token"

    @patch.dict("os.environ", {}, clear=True)
    @patch("claude_meter.api.Path")
    def test_missing_credentials(self, mock_path_cls):
        mock_path_cls.home.return_value.__truediv__ = lambda *a: mock_path_cls.home.return_value
        mock_creds_path = mock_path_cls.home.return_value
        mock_creds_path.exists.return_value = False

        with pytest.raises(SystemExit, match="No credentials found"):
            get_access_token()


class TestFetchUsage:
    @patch("claude_meter.api.urllib.request.urlopen")
    def test_success(self, mock_urlopen):
        data = {
            "five_hour": {"utilization": 0.27, "resets_at": "2026-01-01T00:00:00Z"},
            "seven_day": {"utilization": 0.5, "resets_at": None},
            "ignored_null": None,
        }
        mock_resp = MagicMock()
        mock_resp.read.return_value = json.dumps(data).encode()
        mock_resp.__enter__ = lambda s: s
        mock_resp.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_resp

        limits = fetch_usage("tok")
        assert len(limits) == 2
        assert limits[0]["name"] == "5-Hour Session"
        assert limits[0]["utilization"] == 0.27

    @patch("claude_meter.api.urllib.request.urlopen")
    def test_401_raises_system_exit(self, mock_urlopen):
        mock_urlopen.side_effect = urllib.error.HTTPError(
            url="", code=401, msg="", hdrs=None, fp=None
        )
        with pytest.raises(SystemExit, match="Authentication failed"):
            fetch_usage("tok")

    @patch("claude_meter.api.urllib.request.urlopen")
    def test_500_raises_fetch_error(self, mock_urlopen):
        mock_urlopen.side_effect = urllib.error.HTTPError(
            url="", code=500, msg="", hdrs=None, fp=None
        )
        with pytest.raises(FetchError, match="HTTP 500"):
            fetch_usage("tok")

    @patch("claude_meter.api.urllib.request.urlopen")
    def test_network_error_raises_fetch_error(self, mock_urlopen):
        mock_urlopen.side_effect = urllib.error.URLError("Connection refused")
        with pytest.raises(FetchError, match="Could not reach API"):
            fetch_usage("tok")

    @patch("claude_meter.api.urllib.request.urlopen")
    def test_null_utilization_skipped(self, mock_urlopen):
        data = {"five_hour": {"utilization": None, "resets_at": None}}
        mock_resp = MagicMock()
        mock_resp.read.return_value = json.dumps(data).encode()
        mock_resp.__enter__ = lambda s: s
        mock_resp.__exit__ = MagicMock(return_value=False)
        mock_urlopen.return_value = mock_resp

        limits = fetch_usage("tok")
        assert limits == []
