"""Tests for the post-push hook templates.

These tests verify the main() function of each template behaves correctly
under various scenarios (CI pass/fail, deploy pass/fail, health check).
"""

import json
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

HOOK_HELPERS_DIR = str(Path(__file__).resolve().parent.parent / "hook-helpers")
TEMPLATES_DIR = Path(__file__).resolve().parent.parent / "hook-templates"

if HOOK_HELPERS_DIR not in sys.path:
    sys.path.insert(0, HOOK_HELPERS_DIR)


def _load_template_module(name: str):
    """Import a hook template as a module."""
    import importlib.util
    spec = importlib.util.spec_from_file_location(
        name, TEMPLATES_DIR / f"{name}.py",
    )
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod


# -----------------------------------------------------------------------
# CI + Deploy template
# -----------------------------------------------------------------------

class TestCiAndDeployTemplate:
    @pytest.fixture(autouse=True)
    def env(self, tmp_path, monkeypatch):
        monkeypatch.setenv("AUTOBUILDER_PROJECT_DIR", str(tmp_path))
        monkeypatch.setenv("AUTOBUILDER_PROJECT_ID", "test-app")
        monkeypatch.setenv("AUTOBUILDER_PORT", "3080")
        monkeypatch.setenv("AUTOBUILDER_PUSH_BRANCH", "main")
        monkeypatch.setenv("AUTOBUILDER_EXECUTOR", "cursor")
        monkeypatch.setenv("AUTOBUILDER_MODEL", "")
        self.project_dir = tmp_path

    def _load(self):
        return _load_template_module("post-push-ci-and-deploy")

    def test_no_remote_exits_0(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=None):
            assert mod.main() == 0

    def test_no_workflows_exits_0(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=("owner", "repo")), \
             patch.object(mod, "has_github_workflows", return_value=False):
            assert mod.main() == 0

    def test_ci_passes_no_deploy_exits_0(self):
        """With no deploy.json, just CI monitoring — exits 0 on CI pass."""
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=("owner", "repo")), \
             patch.object(mod, "has_github_workflows", return_value=True), \
             patch.object(mod, "_run_ci_pipeline", return_value=True):
            assert mod.main() == 0

    def test_ci_fails_exits_1(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=("owner", "repo")), \
             patch.object(mod, "has_github_workflows", return_value=True), \
             patch.object(mod, "_run_ci_pipeline", return_value=False):
            assert mod.main() == 1

    def test_deploy_succeeds(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=("owner", "repo")), \
             patch.object(mod, "has_github_workflows", return_value=False), \
             patch.object(mod, "_load_config", return_value={
                 **mod.DEFAULTS, "deploy_domain": "example.com",
             }), \
             patch.object(mod, "_run_deploy_pipeline", return_value=True):
            assert mod.main() == 0

    def test_deploy_fails_exits_1(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=("owner", "repo")), \
             patch.object(mod, "has_github_workflows", return_value=False), \
             patch.object(mod, "_load_config", return_value={
                 **mod.DEFAULTS, "deploy_domain": "example.com",
             }), \
             patch.object(mod, "_run_deploy_pipeline", return_value=False):
            assert mod.main() == 1

    def test_no_deploy_domain_skips_deploy(self):
        """Default config (no deploy_domain) should not run deploy at all."""
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=("owner", "repo")), \
             patch.object(mod, "has_github_workflows", return_value=False):
            assert mod.main() == 0

    def test_no_port_skips_deploy(self, monkeypatch):
        monkeypatch.setenv("AUTOBUILDER_PORT", "0")
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=("owner", "repo")), \
             patch.object(mod, "has_github_workflows", return_value=False), \
             patch.object(mod, "_load_config", return_value={
                 **mod.DEFAULTS, "deploy_domain": "example.com",
             }):
            assert mod.main() == 0

    def test_deploy_pipeline_health_check_failure_returns_false(self):
        """Regression: Bug A + Bug C — deploy failure must propagate."""
        mod = self._load()
        cfg = dict(mod.DEFAULTS)
        cfg["deploy_domain"] = "example.com"
        cfg["max_deploy_fix_attempts"] = 0

        with patch.object(mod, "run_deploy_script", return_value=(True, "ok")), \
             patch.object(mod, "health_check", return_value=False):
            result = mod._run_deploy_pipeline(
                self.project_dir, "test-app", 3080, cfg, "cursor", "",
            )
        assert result is False

    def test_deploy_pipeline_exhausted_retries_returns_false(self):
        """Regression: Bug A — must return False after exhausting retries."""
        mod = self._load()
        cfg = dict(mod.DEFAULTS)
        cfg["deploy_domain"] = "example.com"
        cfg["max_deploy_fix_attempts"] = 1

        with patch.object(mod, "run_deploy_script", return_value=(False, "error")), \
             patch.object(mod, "run_fix_agent", return_value=0):
            result = mod._run_deploy_pipeline(
                self.project_dir, "test-app", 3080, cfg, "cursor", "",
            )
        assert result is False

    def test_deploy_pipeline_success_stores_url(self):
        mod = self._load()
        cfg = dict(mod.DEFAULTS)
        cfg["deploy_domain"] = "example.com"

        with patch.object(mod, "run_deploy_script", return_value=(True, "ok")), \
             patch.object(mod, "health_check", return_value=True), \
             patch.object(mod, "store_deploy_url") as mock_store:
            result = mod._run_deploy_pipeline(
                self.project_dir, "test-app", 3080, cfg, "cursor", "",
            )
        assert result is True
        mock_store.assert_called_once_with(
            self.project_dir, "https://test-app.example.com",
        )

    def test_deploy_pipeline_no_domain_skips(self):
        mod = self._load()
        cfg = dict(mod.DEFAULTS)
        cfg["deploy_domain"] = ""

        result = mod._run_deploy_pipeline(
            self.project_dir, "test-app", 3080, cfg, "cursor", "",
        )
        assert result is True

    def test_deploy_config_passes_ssh_key_and_app_dir(self):
        """Verify fix_agent receives ssh_key and app_dir from config."""
        mod = self._load()
        cfg = dict(mod.DEFAULTS)
        cfg["deploy_domain"] = "example.com"
        cfg["server_host"] = "1.2.3.4"
        cfg["server_user"] = "deploy"
        cfg["ssh_key"] = "~/.ssh/custom_key"
        cfg["app_dir"] = "/opt/apps/my-app"
        cfg["max_deploy_fix_attempts"] = 1

        with patch.object(mod, "run_deploy_script", return_value=(False, "error")), \
             patch.object(mod, "build_deploy_fix_prompt") as mock_prompt, \
             patch.object(mod, "run_fix_agent", return_value=0):
            mod._run_deploy_pipeline(
                self.project_dir, "test-app", 3080, cfg, "cursor", "",
            )

        call_kwargs = mock_prompt.call_args[1]
        assert call_kwargs["ssh_key"] == "~/.ssh/custom_key"
        assert call_kwargs["app_dir"] == "/opt/apps/my-app"


# -----------------------------------------------------------------------
# CI + Vercel template
# -----------------------------------------------------------------------

class TestCiAndVercelTemplate:
    @pytest.fixture(autouse=True)
    def env(self, tmp_path, monkeypatch):
        monkeypatch.setenv("AUTOBUILDER_PROJECT_DIR", str(tmp_path))
        monkeypatch.setenv("AUTOBUILDER_PROJECT_ID", "test-app")
        monkeypatch.setenv("AUTOBUILDER_PUSH_BRANCH", "main")
        monkeypatch.setenv("AUTOBUILDER_EXECUTOR", "cursor")
        monkeypatch.setenv("AUTOBUILDER_MODEL", "")
        self.project_dir = tmp_path

    def _load(self):
        return _load_template_module("post-push-ci-and-vercel")

    def test_vercel_deploy_succeeds(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=None), \
             patch.object(
                 mod, "wait_for_vercel_deploy",
                 return_value=(True, "https://my-app.vercel.app"),
             ), \
             patch.object(mod, "health_check", return_value=True), \
             patch.object(mod, "store_deploy_url") as mock_store:
            assert mod.main() == 0
        mock_store.assert_called_once_with(
            self.project_dir, "https://my-app.vercel.app",
        )

    def test_vercel_deploy_fails_exits_1(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=None), \
             patch.object(
                 mod, "wait_for_vercel_deploy",
                 return_value=(False, ""),
             ):
            assert mod.main() == 1

    def test_vercel_health_check_fails_exits_1(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=None), \
             patch.object(
                 mod, "wait_for_vercel_deploy",
                 return_value=(True, "https://my-app.vercel.app"),
             ), \
             patch.object(mod, "health_check", return_value=False):
            assert mod.main() == 1

    def test_ci_failure_blocks_vercel_deploy(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=("owner", "repo")), \
             patch.object(mod, "has_github_workflows", return_value=True), \
             patch.object(mod, "_run_ci_pipeline", return_value=False):
            assert mod.main() == 1

    def test_ci_passes_then_vercel_deploy(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=("owner", "repo")), \
             patch.object(mod, "has_github_workflows", return_value=True), \
             patch.object(mod, "_run_ci_pipeline", return_value=True), \
             patch.object(
                 mod, "wait_for_vercel_deploy",
                 return_value=(True, "https://my-app.vercel.app"),
             ), \
             patch.object(mod, "health_check", return_value=True), \
             patch.object(mod, "store_deploy_url"):
            assert mod.main() == 0

    def test_vercel_empty_url_skips_health_check(self):
        mod = self._load()
        with patch.object(mod, "get_github_remote", return_value=None), \
             patch.object(
                 mod, "wait_for_vercel_deploy",
                 return_value=(True, ""),
             ), \
             patch.object(mod, "health_check") as mock_hc:
            assert mod.main() == 0
        mock_hc.assert_not_called()


# -----------------------------------------------------------------------
# Config loading
# -----------------------------------------------------------------------

class TestConfigLoading:
    def test_deploy_loads_defaults_when_no_file(self, tmp_path, monkeypatch):
        monkeypatch.setenv("AUTOBUILDER_PROJECT_DIR", str(tmp_path))
        mod = _load_template_module("post-push-ci-and-deploy")
        cfg = mod._load_config(tmp_path)
        assert cfg["max_ci_fix_attempts"] == 3
        assert cfg["ci_timeout"] == 600
        assert cfg["deploy_domain"] == ""

    def test_deploy_loads_deploy_json(self, tmp_path, monkeypatch):
        monkeypatch.setenv("AUTOBUILDER_PROJECT_DIR", str(tmp_path))
        ab = tmp_path / ".autobuilder"
        ab.mkdir()
        (ab / "deploy.json").write_text(json.dumps({
            "deploy_domain": "mysite.com",
            "server_host": "10.0.0.1",
        }))
        mod = _load_template_module("post-push-ci-and-deploy")
        cfg = mod._load_config(tmp_path)
        assert cfg["deploy_domain"] == "mysite.com"
        assert cfg["server_host"] == "10.0.0.1"
        assert cfg["max_ci_fix_attempts"] == 3

    def test_vercel_loads_custom_timeout(self, tmp_path, monkeypatch):
        monkeypatch.setenv("AUTOBUILDER_PROJECT_DIR", str(tmp_path))
        ab = tmp_path / ".autobuilder"
        ab.mkdir()
        (ab / "deploy.json").write_text(json.dumps({
            "vercel_timeout": 300,
        }))
        mod = _load_template_module("post-push-ci-and-vercel")
        cfg = mod._load_config(tmp_path)
        assert cfg.get("vercel_timeout") == 300

    def test_invalid_json_uses_defaults(self, tmp_path, monkeypatch):
        monkeypatch.setenv("AUTOBUILDER_PROJECT_DIR", str(tmp_path))
        ab = tmp_path / ".autobuilder"
        ab.mkdir()
        (ab / "deploy.json").write_text("not json!!!")
        mod = _load_template_module("post-push-ci-and-deploy")
        cfg = mod._load_config(tmp_path)
        assert cfg["max_ci_fix_attempts"] == 3
