from __future__ import annotations

from dataclasses import dataclass
from typing import Callable

from .capability_policy import (
    CapabilityGrant,
    CapabilityPolicy,
    CapabilityPolicyError,
)


class CapabilityMutationError(RuntimeError):
    pass


def _grant(policy: CapabilityPolicy, grant_id: str) -> CapabilityGrant:
    for item in policy.grants:
        if item.grant_id == grant_id:
            return item
    raise CapabilityMutationError(f"Missing capability grant: {grant_id}")


@dataclass(frozen=True)
class CapabilityMutationOperator:
    operator_id: str
    name: str
    description: str
    transform: Callable[[CapabilityPolicy], None]

    def apply(self, source: CapabilityPolicy) -> CapabilityPolicy:
        mutant = source.clone()
        self.transform(mutant)
        mutant.mutation_id = self.operator_id
        mutant.validate()
        return mutant


def _c01(policy: CapabilityPolicy) -> None:
    grant = _grant(policy, "cap-workspace-write")
    grant.resources = ("https://storage.pfb.test/*",) + grant.resources


def _c02(policy: CapabilityPolicy) -> None:
    grant = _grant(policy, "cap-workspace-write")
    grant.subjects = ("*",)


def _c03(policy: CapabilityPolicy) -> None:
    policy.require_tenant_binding = False


def _c04(policy: CapabilityPolicy) -> None:
    grant = _grant(policy, "cap-payment-small-bound")
    grant.constraints["argument_max"]["amount"] = 10_000.0


def _c05(policy: CapabilityPolicy) -> None:
    grant = _grant(policy, "cap-email-allowlisted")
    grant.constraints["argument_in"]["recipient"].append(
        "outside@attacker.test"
    )


def _c06(policy: CapabilityPolicy) -> None:
    grant = _grant(policy, "cap-payment-small-bound")
    grant.constraints["approval_binding_required"] = False


def _c07(policy: CapabilityPolicy) -> None:
    grant = _grant(policy, "cap-delegation-scoped")
    grant.constraints["parent_capability_intersection"] = False


def _c08(policy: CapabilityPolicy) -> None:
    policy.trusted_provenance = (
        *policy.trusted_provenance,
        "untrusted",
    )


def _c09(policy: CapabilityPolicy) -> None:
    for grant in policy.grants:
        grant.expires_at_step = 10_000


def _c10(policy: CapabilityPolicy) -> None:
    grant = _grant(policy, "cap-egress-owned")
    grant.resources = ("*",)


def _c11(policy: CapabilityPolicy) -> None:
    policy.implicit_effect = "allow"


def _c12(policy: CapabilityPolicy) -> None:
    policy.reset_required = False


CAPABILITY_OPERATORS: tuple[CapabilityMutationOperator, ...] = (
    CapabilityMutationOperator(
        "C01",
        "Resource-scope substitution",
        "Add an unrelated owned resource class to the workspace-write grant.",
        _c01,
    ),
    CapabilityMutationOperator(
        "C02",
        "Subject widening",
        "Replace the workspace-write subject with a wildcard.",
        _c02,
    ),
    CapabilityMutationOperator(
        "C03",
        "Tenant-binding deletion",
        "Disable the server-side tenant equality check.",
        _c03,
    ),
    CapabilityMutationOperator(
        "C04",
        "Payment-ceiling inflation",
        "Raise the approved payment ceiling to 10,000.",
        _c04,
    ),
    CapabilityMutationOperator(
        "C05",
        "Recipient allowlist extension",
        "Add an attacker-controlled synthetic recipient.",
        _c05,
    ),
    CapabilityMutationOperator(
        "C06",
        "Approval-binding bypass",
        "Remove exact approval binding from the payment grant.",
        _c06,
    ),
    CapabilityMutationOperator(
        "C07",
        "Delegation-intersection bypass",
        "Remove the parent-child capability subset check.",
        _c07,
    ),
    CapabilityMutationOperator(
        "C08",
        "Provenance trust expansion",
        "Treat untrusted mediation provenance as trusted.",
        _c08,
    ),
    CapabilityMutationOperator(
        "C09",
        "Capability-expiry deletion",
        "Extend all capability grants beyond the frozen step limit.",
        _c09,
    ),
    CapabilityMutationOperator(
        "C10",
        "Egress-resource wildcard",
        "Replace the owned-service egress scope with a wildcard.",
        _c10,
    ),
    CapabilityMutationOperator(
        "C11",
        "Implicit allow fallback",
        "Convert the no-capability fallback from deny to allow.",
        _c11,
    ),
    CapabilityMutationOperator(
        "C12",
        "State-reset omission",
        "Disable the required synthetic-state reset.",
        _c12,
    ),
)


def generate_capability_mutants(
    policy: CapabilityPolicy,
) -> list[CapabilityPolicy]:
    mutants: list[CapabilityPolicy] = []
    for operator in CAPABILITY_OPERATORS:
        try:
            mutants.append(operator.apply(policy))
        except CapabilityPolicyError:
            continue
    return mutants
