from __future__ import annotations

import copy
import hashlib
import importlib
import json
import subprocess
import sys
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[1]
OPS = ROOT / "ops"
sys.path.insert(0, str(OPS))
apply = importlib.import_module("apply_gcp_iap_operator_access")
sys.path.pop(0)


def _binding(role: str, member: str, condition: tuple[str, str] | None = None) -> dict[str, Any]:
    result: dict[str, Any] = {"role": role, "members": [member]}
    if condition:
        result["condition"] = {"title": condition[0], "expression": condition[1]}
    return result


def _full_state() -> dict[str, Any]:
    operator_members = [f"serviceAccount:{email}" for _, email, _ in apply.SERVICE_ACCOUNTS]
    account_policies = {
        email: {"bindings": [_binding("roles/iam.workloadIdentityUser", apply.WIF_PRINCIPAL)]}
        for _, email, _ in apply.SERVICE_ACCOUNTS
    }
    project_bindings: list[dict[str, Any]] = []
    for member in operator_members:
        project_bindings.extend(
            [
                _binding(apply.CUSTOM_ROLE, member),
                _binding(apply.IAP_ROLE, member, (apply.IAP_CONDITION_TITLE, apply.IAP_CONDITION)),
            ]
        )
    return {
        "services": set(apply.REQUIRED_APIS),
        "pool": {
            "name": f"projects/123456789/locations/global/workloadIdentityPools/{apply.POOL}",
            "state": "ACTIVE",
            "disabled": False,
        },
        "provider": {
            "name": (
                f"projects/123456789/locations/global/workloadIdentityPools/{apply.POOL}"
                f"/providers/{apply.PROVIDER}"
            ),
            "state": "ACTIVE",
            "disabled": False,
            "displayName": apply.PROVIDER_DISPLAY_NAME,
            "oidc": {"issuerUri": apply.PROVIDER_ISSUER},
            "attributeMapping": apply.EXPECTED_ATTRIBUTE_MAPPING,
            "attributeCondition": apply.PROVIDER_CONDITION,
        },
        "accounts": {
            email: {"email": email, "displayName": display_name, "disabled": False}
            for _, email, display_name in apply.SERVICE_ACCOUNTS
        },
        "account_policies": account_policies,
        "custom_role": {
            "title": apply.CUSTOM_ROLE_TITLE,
            "description": apply.CUSTOM_ROLE_DESCRIPTION,
            "includedPermissions": list(apply.CUSTOM_PERMISSIONS),
            "stage": "GA",
        },
        "project_policy": {"bindings": project_bindings},
        "vm_policy": {"bindings": [_binding("roles/iam.serviceAccountUser", member) for member in operator_members]},
        "instance": {
            "status": "RUNNING",
            "networkInterfaces": [
                {
                    "networkIP": apply.INSTANCE_INTERNAL_IP,
                    "network": f"projects/{apply.PROJECT}/global/networks/{apply.NETWORK}",
                }
            ],
            "serviceAccounts": [{"email": apply.VM_SERVICE_ACCOUNT}],
            "metadata": {"items": [{"key": "enable-oslogin", "value": "TRUE"}]},
            "tags": {"items": [apply.TARGET_TAG]},
        },
        "instance_policy": {
            "bindings": [
                _binding("roles/compute.osLogin", operator_members[0]),
                _binding("roles/compute.osAdminLogin", operator_members[1]),
            ]
        },
        "iap_firewall": {
            "direction": "INGRESS",
            "network": f"projects/{apply.PROJECT}/global/networks/{apply.NETWORK}",
            "allowed": [{"IPProtocol": "tcp", "ports": ["22"]}],
            "sourceRanges": [apply.IAP_SOURCE_RANGE],
            "targetTags": [apply.TARGET_TAG],
            "disabled": False,
            "logConfig": {"enable": True},
        },
        "public_firewall": {
            "name": apply.CHANGING_PUBLIC_RULE,
            "disabled": False,
            "targetTags": ["teleo-public-ssh-bootstrap"],
        },
        "dispatcher_hash": hashlib.sha256(apply.DEFAULT_DISPATCHER.read_bytes()).hexdigest(),
        "dispatcher_file_mode": "root:root:755",
        "dispatcher_run_mode": "root:root:700",
    }


def _clean_state() -> dict[str, Any]:
    state = _full_state()
    state.update(
        {
            "services": set(),
            "pool": None,
            "provider": None,
            "accounts": {email: None for _, email, _ in apply.SERVICE_ACCOUNTS},
            "account_policies": {email: None for _, email, _ in apply.SERVICE_ACCOUNTS},
            "custom_role": None,
            "project_policy": {"bindings": []},
            "vm_policy": {"bindings": []},
            "instance_policy": {"bindings": []},
            "iap_firewall": None,
            "dispatcher_hash": None,
            "dispatcher_file_mode": None,
            "dispatcher_run_mode": None,
        }
    )
    state["instance"]["tags"] = {"items": []}
    return state


class FakeGcloud:
    def __init__(self, state: dict[str, Any]) -> None:
        self.state = copy.deepcopy(state)
        self.commands: list[list[str]] = []
        self.mutations: list[list[str]] = []

    @staticmethod
    def _value(command: list[str], flag: str) -> str:
        return command[command.index(flag) + 1]

    @staticmethod
    def _completed(command: list[str], payload: Any = None, *, returncode: int = 0, stderr: str = ""):
        stdout = "" if payload is None else json.dumps(payload)
        return subprocess.CompletedProcess(command, returncode, stdout, stderr)

    @classmethod
    def _missing(cls, command: list[str]):
        return cls._completed(command, returncode=1, stderr="ERROR: NOT_FOUND: resource does not exist")

    @staticmethod
    def _add_binding(policy: dict[str, Any], role: str, member: str, condition: tuple[str, str] | None) -> None:
        candidate = _binding(role, member, condition)
        if candidate not in policy["bindings"]:
            policy["bindings"].append(candidate)

    def _read(self, command: list[str]):
        words = command[1:]
        if words[:2] == ["projects", "describe"]:
            return self._completed(command, {"projectId": apply.PROJECT})
        if words[:2] == ["services", "list"]:
            return self._completed(command, [{"config": {"name": name}} for name in sorted(self.state["services"])])
        if words[:3] == ["iam", "workload-identity-pools", "describe"]:
            value = self.state["pool"]
            return self._missing(command) if value is None or value.get("state") == "DELETED" else self._completed(command, value)
        if words[:3] == ["iam", "workload-identity-pools", "list"]:
            value = self.state["pool"]
            return self._completed(command, [] if value is None else [value])
        if words[:4] == ["iam", "workload-identity-pools", "providers", "describe"]:
            value = self.state["provider"]
            return self._missing(command) if value is None or value.get("state") == "DELETED" else self._completed(command, value)
        if words[:4] == ["iam", "workload-identity-pools", "providers", "list"]:
            value = self.state["provider"]
            return self._completed(command, [] if value is None else [value])
        if words[:3] == ["iam", "service-accounts", "describe"]:
            email = words[3]
            value = self.state["accounts"].get(email)
            return self._missing(command) if value is None else self._completed(command, value)
        if words[:3] == ["iam", "service-accounts", "get-iam-policy"]:
            email = words[3]
            value = (
                self.state["vm_policy"] if email == apply.VM_SERVICE_ACCOUNT else self.state["account_policies"][email]
            )
            return self._completed(command, value)
        if words[:3] == ["iam", "roles", "describe"]:
            value = self.state["custom_role"]
            return self._missing(command) if value is None else self._completed(command, value)
        if words[:2] == ["projects", "get-iam-policy"]:
            return self._completed(command, self.state["project_policy"])
        if words[:3] == ["compute", "instances", "describe"]:
            return self._completed(command, self.state["instance"])
        if words[:3] == ["compute", "instances", "get-iam-policy"]:
            return self._completed(command, self.state["instance_policy"])
        if words[:3] == ["compute", "firewall-rules", "describe"]:
            rule = words[3]
            value = self.state["iap_firewall"] if rule == apply.IAP_FIREWALL_RULE else self.state["public_firewall"]
            return self._missing(command) if value is None else self._completed(command, value)
        return None

    def _mutate(self, command: list[str]):
        words = command[1:]
        if words[:2] == ["services", "enable"]:
            self.state["services"].update(words[2 : words.index("--project")])
        elif words[:3] == ["iam", "workload-identity-pools", "create"]:
            self.state["pool"] = _full_state()["pool"]
        elif words[:3] == ["iam", "workload-identity-pools", "undelete"]:
            self.state["pool"]["state"] = "ACTIVE"
        elif words[:3] == ["iam", "workload-identity-pools", "update"]:
            self.state["pool"]["disabled"] = False
        elif words[:4] == ["iam", "workload-identity-pools", "providers", "create-oidc"]:
            self.state["provider"] = _full_state()["provider"]
        elif words[:4] == ["iam", "workload-identity-pools", "providers", "undelete"]:
            self.state["provider"]["state"] = "ACTIVE"
        elif words[:4] == ["iam", "workload-identity-pools", "providers", "update-oidc"]:
            self.state["provider"].update(
                {
                    "state": "ACTIVE",
                    "disabled": False,
                    "displayName": self._value(command, "--display-name"),
                    "oidc": {"issuerUri": self._value(command, "--issuer-uri")},
                    "attributeMapping": apply.EXPECTED_ATTRIBUTE_MAPPING,
                    "attributeCondition": self._value(command, "--attribute-condition"),
                }
            )
        elif words[:3] == ["iam", "service-accounts", "create"]:
            account_id = words[3]
            email = f"{account_id}@{apply.PROJECT}.iam.gserviceaccount.com"
            self.state["accounts"][email] = {
                "email": email,
                "displayName": self._value(command, "--display-name"),
                "disabled": False,
            }
            self.state["account_policies"][email] = {"bindings": []}
        elif words[:3] == ["iam", "service-accounts", "update"]:
            self.state["accounts"][words[3]]["displayName"] = self._value(command, "--display-name")
        elif words[:3] == ["iam", "roles", "create"] or words[:3] == ["iam", "roles", "update"]:
            self.state["custom_role"] = {
                "title": self._value(command, "--title"),
                "description": self._value(command, "--description"),
                "includedPermissions": self._value(command, "--permissions").split(","),
                "stage": self._value(command, "--stage"),
            }
        elif words[:3] == ["iam", "service-accounts", "add-iam-policy-binding"]:
            email = words[3]
            policy = (
                self.state["vm_policy"] if email == apply.VM_SERVICE_ACCOUNT else self.state["account_policies"][email]
            )
            self._add_binding(policy, self._value(command, "--role"), self._value(command, "--member"), None)
        elif words[:2] == ["projects", "add-iam-policy-binding"]:
            condition = None
            if "--condition" in command:
                expression, title = self._value(command, "--condition").rsplit(",title=", 1)
                condition = (title, expression.removeprefix("expression="))
            self._add_binding(
                self.state["project_policy"],
                self._value(command, "--role"),
                self._value(command, "--member"),
                condition,
            )
        elif words[:3] == ["compute", "instances", "add-iam-policy-binding"]:
            self._add_binding(
                self.state["instance_policy"],
                self._value(command, "--role"),
                self._value(command, "--member"),
                None,
            )
        elif words[:3] == ["compute", "instances", "add-metadata"]:
            self.state["instance"]["metadata"] = {"items": [{"key": "enable-oslogin", "value": "TRUE"}]}
        elif words[:3] == ["compute", "instances", "add-tags"]:
            self.state["instance"]["tags"] = {"items": [apply.TARGET_TAG]}
        elif words[:3] == ["compute", "firewall-rules", "create"]:
            self.state["iap_firewall"] = _full_state()["iap_firewall"]
        elif words[:3] == ["compute", "firewall-rules", "update"]:
            self.state["iap_firewall"].update(
                {"targetTags": [apply.TARGET_TAG], "disabled": False, "logConfig": {"enable": True}}
            )
        elif words[:2] == ["compute", "scp"]:
            pass
        elif words[:2] == ["compute", "ssh"] and "sudo install -o root" in self._value(command, "--command"):
            self.state["dispatcher_hash"] = hashlib.sha256(apply.DEFAULT_DISPATCHER.read_bytes()).hexdigest()
            self.state["dispatcher_file_mode"] = "root:root:755"
            self.state["dispatcher_run_mode"] = "root:root:700"
        else:
            return None
        self.mutations.append(command)
        return self._completed(command)

    def __call__(self, command: list[str], **_: Any):
        self.commands.append(command)
        read = self._read(command)
        if read is not None:
            return read
        if command[1:3] == ["compute", "ssh"]:
            remote = self._value(command, "--command")
            if "sudo install -o root" in remote:
                return self._mutate(command)
            file_mode = self.state["dispatcher_file_mode"] or "MISSING"
            run_mode = self.state["dispatcher_run_mode"] or "MISSING"
            lines = [f"FILE={file_mode}"]
            if self.state["dispatcher_hash"]:
                lines.append(f"SHA={self.state['dispatcher_hash']}")
            lines.append(f"RUN={run_mode}")
            return subprocess.CompletedProcess(command, 0, "\n".join(lines) + "\n", "")
        mutated = self._mutate(command)
        if mutated is not None:
            return mutated
        return subprocess.CompletedProcess(command, 2, "", "unsupported fake gcloud command")


def _run(tmp_path: Path, state: dict[str, Any]):
    fake = FakeGcloud(state)
    receipt, exitcode = apply.apply_operator_access(
        execute=True,
        output=tmp_path / "receipt.json",
        runner=fake,
    )
    return fake, receipt, exitcode


def test_clean_create_converges_and_retains_sanitized_receipt(tmp_path: Path) -> None:
    fake, receipt, exitcode = _run(tmp_path, _clean_state())

    assert exitcode == 0
    assert receipt["status"] == "applied"
    assert receipt["mutation_count"] > 0
    assert fake.state["provider"]["attributeCondition"] == apply.PROVIDER_CONDITION
    assert fake.state["dispatcher_hash"] == receipt["dispatcher"]["sha256"]
    assert receipt["credentials_logged"] is False
    assert len(apply.PROVIDER_DISPLAY_NAME) <= 32
    assert apply.PROVIDER_DISPLAY_NAME == "Teleo fixed IAP operator"
    provider_lists = [
        command
        for command in fake.commands
        if command[1:5] == ["iam", "workload-identity-pools", "providers", "list"]
    ]
    assert provider_lists == []
    assert json.loads((tmp_path / "receipt.json").read_text())["status"] == "applied"


def test_fully_existing_state_is_control_plane_noop(tmp_path: Path) -> None:
    fake, receipt, exitcode = _run(tmp_path, _full_state())

    assert exitcode == 0
    assert receipt["status"] == "applied"
    assert receipt["mutation_count"] == 0
    assert fake.mutations == []


def test_exact_sixth_mutation_round_can_converge(tmp_path: Path, monkeypatch) -> None:
    discovery_round = 0

    def fake_discover(_client):
        nonlocal discovery_round
        state = {
            "round": discovery_round,
            "public_firewall": {
                "name": apply.CHANGING_PUBLIC_RULE,
                "disabled": False,
                "targetTags": ["teleo-public-ssh-bootstrap"],
            },
        }
        discovery_round += 1
        return state

    def fake_plan(state):
        if state["round"] >= 6:
            return []
        return [apply.Mutation(f"round_{state['round']}", "update", ["gcloud", "noop"])]

    monkeypatch.setattr(apply, "discover", fake_discover)
    monkeypatch.setattr(apply, "plan_mutations", fake_plan)
    monkeypatch.setattr(apply, "_run_mutations", lambda _client, _mutations: None)
    monkeypatch.setattr(apply, "converge_dispatcher", lambda _client, _path: {"status": "unchanged"})

    receipt, exitcode = apply.apply_operator_access(
        execute=True,
        output=tmp_path / "receipt.json",
        runner=lambda command, **_: subprocess.CompletedProcess(command, 0, "", ""),
    )

    assert exitcode == 0
    assert receipt["status"] == "applied"
    assert discovery_round == 7


def test_partial_state_recovers_only_missing_clone_identity_and_bindings(tmp_path: Path) -> None:
    state = _full_state()
    clone_member = f"serviceAccount:{apply.CLONE_SERVICE_ACCOUNT}"
    state["accounts"][apply.CLONE_SERVICE_ACCOUNT] = None
    state["account_policies"][apply.CLONE_SERVICE_ACCOUNT] = None
    for policy_name in ("project_policy", "vm_policy", "instance_policy"):
        state[policy_name]["bindings"] = [
            binding for binding in state[policy_name]["bindings"] if clone_member not in binding["members"]
        ]

    fake, receipt, exitcode = _run(tmp_path, state)
    mutation_names = [
        operation["name"] for operation in receipt["operations"] if operation["action"] not in {"probe", "verify"}
    ]

    assert exitcode == 0
    assert f"create_service_account:{apply.CLONE_SERVICE_ACCOUNT}" in mutation_names
    assert f"create_service_account:{apply.STATUS_SERVICE_ACCOUNT}" not in mutation_names
    assert all(apply.STATUS_SERVICE_ACCOUNT not in " ".join(command) for command in fake.mutations)


def test_security_sensitive_provider_drift_fails_before_mutation(tmp_path: Path) -> None:
    state = _full_state()
    state["provider"]["attributeCondition"] = "assertion.repository == 'attacker/repository'"

    fake, receipt, exitcode = _run(tmp_path, state)

    assert exitcode == 1
    assert receipt["status"] == "failed"
    assert "security-sensitive drift" in receipt["error"]
    assert fake.mutations == []


def test_disabled_pool_and_provider_are_narrowly_reenabled(tmp_path: Path) -> None:
    state = _full_state()
    state["pool"]["disabled"] = True
    state["provider"]["disabled"] = True

    fake, receipt, exitcode = _run(tmp_path, state)
    mutation_names = [
        operation["name"] for operation in receipt["operations"] if operation["action"] not in {"probe", "verify"}
    ]

    assert exitcode == 0
    assert "enable_wif_pool" in mutation_names
    assert "update_wif_provider" in mutation_names
    assert fake.state["pool"]["disabled"] is False
    assert fake.state["provider"]["disabled"] is False


def test_soft_deleted_pool_and_provider_are_undeleted_and_converged(tmp_path: Path) -> None:
    state = _full_state()
    state["pool"]["state"] = "DELETED"
    state["provider"]["state"] = "DELETED"

    fake, receipt, exitcode = _run(tmp_path, state)
    mutation_names = [
        operation["name"] for operation in receipt["operations"] if operation["action"] not in {"probe", "verify"}
    ]

    assert exitcode == 0
    assert "undelete_wif_pool" in mutation_names
    assert "undelete_wif_provider" in mutation_names
    assert "create_wif_pool" not in mutation_names
    assert "create_wif_provider" not in mutation_names
    assert fake.state["pool"]["state"] == "ACTIVE"
    assert fake.state["provider"]["state"] == "ACTIVE"


def test_wrong_or_stopped_instance_fails_before_mutation(tmp_path: Path) -> None:
    state = _full_state()
    state["instance"]["status"] = "TERMINATED"

    fake, receipt, exitcode = _run(tmp_path, state)

    assert exitcode == 1
    assert receipt["status"] == "failed"
    assert "not running" in receipt["error"]
    assert fake.mutations == []


def test_missing_os_login_fails_before_mutation(tmp_path: Path) -> None:
    state = _full_state()
    state["instance"]["metadata"] = {"items": []}

    fake, receipt, exitcode = _run(tmp_path, state)

    assert exitcode == 1
    assert receipt["status"] == "failed"
    assert "refusing to change the SSH authentication mode" in receipt["error"]
    assert fake.mutations == []


def test_bootstrap_never_disables_or_mutates_public_ssh_rule(tmp_path: Path) -> None:
    fake, receipt, exitcode = _run(tmp_path, _clean_state())

    assert exitcode == 0
    assert receipt["public_ssh_rule"] == {
        "name": apply.CHANGING_PUBLIC_RULE,
        "observed_disabled_before": False,
        "observed_disabled_after": False,
        "target_tags_before": ["teleo-public-ssh-bootstrap"],
        "target_tags_after": ["teleo-public-ssh-bootstrap"],
        "effective_target_tag_expansion": False,
        "mutated": False,
        "disable_command_present": False,
    }
    assert not any("--disabled" in command for command in fake.commands)
    assert not any(
        apply.CHANGING_PUBLIC_RULE in command
        and command[1:3] == ["compute", "firewall-rules"]
        and command[3] != "describe"
        for command in fake.commands
    )


def test_public_rule_tag_collision_fails_before_mutation(tmp_path: Path) -> None:
    state = _clean_state()
    state["public_firewall"]["targetTags"] = [apply.TARGET_TAG]

    fake, receipt, exitcode = _run(tmp_path, state)

    assert exitcode == 1
    assert receipt["status"] == "failed"
    assert "would newly match the existing public SSH firewall rule" in receipt["error"]
    assert fake.mutations == []
