from __future__ import annotations

import pytest
from unittest.mock import AsyncMock, patch

from aegro.cli._client import SyncAegroClient


class TestSyncAegroClient:
    def test_get(self):
        mock_async = AsyncMock()
        mock_async.get.return_value = {"_id": "abc", "name": "Test"}
        mock_async.close.return_value = None

        with patch("aegro.cli._client.AegroClient", return_value=mock_async):
            client = SyncAegroClient(base_url="https://test.com")
            result = client.get("/path", api_key="key-1")

        assert result == {"_id": "abc", "name": "Test"}
        mock_async.get.assert_called_once_with("/path", api_key="key-1")

    def test_post(self):
        mock_async = AsyncMock()
        mock_async.post.return_value = {"created": True}
        mock_async.close.return_value = None

        with patch("aegro.cli._client.AegroClient", return_value=mock_async):
            client = SyncAegroClient(base_url="https://test.com")
            result = client.post("/path", api_key="key-1", json_body={"a": 1})

        assert result == {"created": True}
        mock_async.post.assert_called_once_with("/path", api_key="key-1", json_body={"a": 1})

    def test_put(self):
        mock_async = AsyncMock()
        mock_async.put.return_value = {"updated": True}
        mock_async.close.return_value = None

        with patch("aegro.cli._client.AegroClient", return_value=mock_async):
            client = SyncAegroClient(base_url="https://test.com")
            result = client.put("/path", api_key="key-1", json_body={"a": 1})

        assert result == {"updated": True}

    def test_delete(self):
        mock_async = AsyncMock()
        mock_async.delete.return_value = None
        mock_async.close.return_value = None

        with patch("aegro.cli._client.AegroClient", return_value=mock_async):
            client = SyncAegroClient(base_url="https://test.com")
            result = client.delete("/path", api_key="key-1")

        assert result is None


import json
from aegro.cli._auth import (
    load_credentials,
    save_credentials,
    remove_credentials,
    validate_api_key,
    CREDENTIALS_FILE_NAME,
)


class TestCredentialStorage:
    def test_save_and_load(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._auth.get_config_dir", lambda: tmp_path)
        monkeypatch.delenv("AEGRO_FARMS", raising=False)
        monkeypatch.delenv("AEGRO_FARMS_FILE", raising=False)
        save_credentials({"Fazenda Sul": "key-1", "Fazenda Norte": "key-2"})
        farms = load_credentials()
        assert farms == {"Fazenda Sul": "key-1", "Fazenda Norte": "key-2"}

    def test_credentials_file_is_valid_json(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._auth.get_config_dir", lambda: tmp_path)
        save_credentials({"Farm": "key-1"})
        data = json.loads((tmp_path / CREDENTIALS_FILE_NAME).read_text())
        assert data["farms"] == {"Farm": "key-1"}

    def test_load_empty_when_no_file(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._auth.get_config_dir", lambda: tmp_path)
        monkeypatch.delenv("AEGRO_FARMS", raising=False)
        monkeypatch.delenv("AEGRO_FARMS_FILE", raising=False)
        assert load_credentials() == {}

    def test_env_var_takes_priority(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._auth.get_config_dir", lambda: tmp_path)
        monkeypatch.setenv("AEGRO_FARMS", '{"EnvFarm": "env-key"}')
        save_credentials({"FileFarm": "file-key"})
        farms = load_credentials()
        assert "EnvFarm" in farms
        assert "FileFarm" not in farms

    def test_env_file_takes_priority_over_credentials(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._auth.get_config_dir", lambda: tmp_path)
        monkeypatch.delenv("AEGRO_FARMS", raising=False)
        farms_file = tmp_path / "farms.json"
        farms_file.write_text('{"FileFarm": "file-key"}')
        monkeypatch.setenv("AEGRO_FARMS_FILE", str(farms_file))
        save_credentials({"CredFarm": "cred-key"})
        farms = load_credentials()
        assert "FileFarm" in farms
        assert "CredFarm" not in farms

    def test_remove_credentials(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._auth.get_config_dir", lambda: tmp_path)
        save_credentials({"Farm": "key"})
        assert remove_credentials() is True
        assert not (tmp_path / CREDENTIALS_FILE_NAME).exists()

    def test_remove_when_none(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._auth.get_config_dir", lambda: tmp_path)
        assert remove_credentials() is False

    def test_load_credentials_cp1252_fallback(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setattr("aegro.cli._auth.get_config_dir", lambda: tmp_path)
        monkeypatch.delenv("AEGRO_FARMS", raising=False)
        monkeypatch.delenv("AEGRO_FARMS_FILE", raising=False)
        # Write credentials with CP1252 encoding (accented farm name)
        data = {"farms": {"Fazenda São João": "key-1"}, "auth_method": "api-key"}
        content = json.dumps(data, ensure_ascii=False)
        (tmp_path / "credentials.json").write_bytes(content.encode("cp1252"))
        farms = load_credentials()
        assert "Fazenda São João" in farms
        captured = capsys.readouterr()
        assert "CP1252" in captured.err

    def test_save_credentials_skips_chmod_on_windows(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._auth.get_config_dir", lambda: tmp_path)
        monkeypatch.setattr("aegro.cli._auth.sys.platform", "win32")
        chmod_calls = []
        monkeypatch.setattr("aegro.cli._auth.os.chmod", lambda *args: chmod_calls.append(args))
        save_credentials({"Farm": "key-1"})
        assert len(chmod_calls) == 0
        # Verify file was still created
        assert (tmp_path / "credentials.json").exists()


class TestValidateApiKey:
    def test_valid_key(self):
        assert validate_api_key("aegro_abc123def456") is True

    def test_empty_key(self):
        assert validate_api_key("") is False

    def test_whitespace_only(self):
        assert validate_api_key("   ") is False


from aegro.config import Settings
from aegro.errors import FarmNotFoundError, NoFarmSelectedError
from aegro.cli._config import (
    get_selected_farm,
    set_selected_farm,
    require_farm,
    get_settings,
    get_client,
)


class TestFarmState:
    def test_no_state_file_returns_none(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._config.get_config_dir", lambda: tmp_path)
        assert get_selected_farm() is None

    def test_set_and_get_farm(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._config.get_config_dir", lambda: tmp_path)
        set_selected_farm("Fazenda Sul")
        assert get_selected_farm() == "Fazenda Sul"

    def test_set_overwrites_previous(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._config.get_config_dir", lambda: tmp_path)
        set_selected_farm("Farm A")
        set_selected_farm("Farm B")
        assert get_selected_farm() == "Farm B"

    def test_state_file_is_valid_json(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._config.get_config_dir", lambda: tmp_path)
        set_selected_farm("Test Farm")
        data = json.loads((tmp_path / "state.json").read_text())
        assert data == {"selected_farm": "Test Farm"}


class TestRequireFarm:
    def test_raises_when_no_farm_selected(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._config.get_config_dir", lambda: tmp_path)
        settings = Settings(farms={"Farm": "key-1"})
        with pytest.raises(NoFarmSelectedError):
            require_farm(settings)

    def test_raises_when_farm_not_in_settings(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._config.get_config_dir", lambda: tmp_path)
        set_selected_farm("Unknown")
        settings = Settings(farms={"Farm": "key-1"})
        with pytest.raises(FarmNotFoundError):
            require_farm(settings)

    def test_returns_farm_name_and_key(self, tmp_path, monkeypatch):
        monkeypatch.setattr("aegro.cli._config.get_config_dir", lambda: tmp_path)
        set_selected_farm("Farm")
        settings = Settings(farms={"Farm": "key-1"})
        name, key = require_farm(settings)
        assert name == "Farm"
        assert key == "key-1"


class TestGetSettings:
    def test_returns_settings_instance(self, monkeypatch):
        monkeypatch.setenv("AEGRO_FARMS", '{"Farm": "key-1"}')
        monkeypatch.setattr("aegro.cli._config._settings", None)
        settings = get_settings()
        assert "Farm" in settings.farms

    def test_caches_settings(self, monkeypatch):
        monkeypatch.setenv("AEGRO_FARMS", '{"Farm": "key-1"}')
        monkeypatch.setattr("aegro.cli._config._settings", None)
        s1 = get_settings()
        s2 = get_settings()
        assert s1 is s2


class TestGetClient:
    def test_creates_client_with_settings(self, monkeypatch):
        monkeypatch.setenv("AEGRO_FARMS", '{"Farm": "key-1"}')
        monkeypatch.setattr("aegro.cli._config._settings", None)
        monkeypatch.setattr("aegro.cli._config._client", None)
        client = get_client()
        assert client._base_url == "https://app.aegro.com.br"


from aegro.cli._output import OutputFormat, format_output


class TestOutputFormat:
    def test_json_dict(self):
        result = format_output({"name": "Soja", "area": 450}, OutputFormat.json)
        data = json.loads(result)
        assert data == {"name": "Soja", "area": 450}

    def test_json_list(self):
        result = format_output([{"a": 1}, {"a": 2}], OutputFormat.json)
        data = json.loads(result)
        assert len(data) == 2

    def test_json_none(self):
        result = format_output(None, OutputFormat.json)
        assert result == "null"

    def test_csv_list_of_dicts(self):
        data = [{"name": "A", "val": 1}, {"name": "B", "val": 2}]
        result = format_output(data, OutputFormat.csv)
        lines = [line.strip() for line in result.strip().splitlines()]
        assert lines[0] == "name,val"
        assert lines[1] == "A,1"
        assert lines[2] == "B,2"

    def test_table_renders_without_error(self):
        data = [{"name": "Soja", "area": 450}]
        result = format_output(data, OutputFormat.table)
        assert "Soja" in result
        assert "450" in result


from aegro.errors import AegroAPIError
from aegro.cli._errors import (
    EXIT_ERROR,
    EXIT_AUTH,
    EXIT_NOT_FOUND,
    handle_error,
)


class TestHandleError:
    def test_no_farm_selected(self, capsys):
        with pytest.raises(SystemExit) as exc_info:
            handle_error(NoFarmSelectedError())
        assert exc_info.value.code == EXIT_AUTH
        captured = capsys.readouterr()
        error = json.loads(captured.err)
        assert error["error"]["code"] == "NO_FARM_SELECTED"

    def test_farm_not_found(self, capsys):
        with pytest.raises(SystemExit) as exc_info:
            handle_error(FarmNotFoundError("Unknown"))
        assert exc_info.value.code == EXIT_NOT_FOUND
        captured = capsys.readouterr()
        error = json.loads(captured.err)
        assert error["error"]["code"] == "FARM_NOT_FOUND"

    def test_auth_error_401(self, capsys):
        with pytest.raises(SystemExit) as exc_info:
            handle_error(AegroAPIError(401, "Unauthorized"))
        assert exc_info.value.code == EXIT_AUTH
        captured = capsys.readouterr()
        error = json.loads(captured.err)
        assert error["error"]["code"] == "AUTH_ERROR"

    def test_api_error_500(self, capsys):
        with pytest.raises(SystemExit) as exc_info:
            handle_error(AegroAPIError(500, "Server error"))
        assert exc_info.value.code == EXIT_ERROR
        captured = capsys.readouterr()
        error = json.loads(captured.err)
        assert error["error"]["code"] == "API_ERROR"

    def test_unexpected_error(self, capsys):
        with pytest.raises(SystemExit) as exc_info:
            handle_error(RuntimeError("boom"))
        assert exc_info.value.code == EXIT_ERROR
        captured = capsys.readouterr()
        # handle_error prints traceback before JSON for unexpected errors,
        # so extract only the last line which contains the JSON payload
        lines = captured.err.strip().splitlines()
        json_line = lines[-1]
        error = json.loads(json_line)
        assert error["error"]["code"] == "UNEXPECTED"


from typer.testing import CliRunner
from aegro.cli import app

runner = CliRunner()


class TestAppShell:
    def test_help_shows_app_name(self):
        result = runner.invoke(app, ["--help"])
        assert result.exit_code == 0
        assert "aegro" in result.stdout.lower() or "Aegro" in result.stdout

    def test_no_args_shows_help(self):
        result = runner.invoke(app, [])
        assert result.exit_code == 0
