from __future__ import annotations

from dataclasses import dataclass
from typing import Callable

from .policy import PolicyError, PolicySet, Rule


class MutationError(RuntimeError):
    pass


@dataclass(frozen=True)
class MutationOperator:
    operator_id: str
    name: str
    description: str
    apply_fn: Callable[[PolicySet], None]

    def apply(self, source: PolicySet) -> PolicySet:
        mutant = source.clone()
        self.apply_fn(mutant)
        mutant.mutation_id = self.operator_id
        mutant.version = f"{source.version}+{self.operator_id}"
        mutant.validate()
        return mutant


def _rule(policy: PolicySet, rule_id: str) -> Rule:
    for item in policy.rules:
        if item.rule_id == rule_id:
            return item
    raise MutationError(f"Required rule not found: {rule_id}")


def _m01(policy: PolicySet) -> None:
    rule = _rule(policy, "deny-untrusted-provenance")
    rule.effect = "allow"


def _m02(policy: PolicySet) -> None:
    _rule(policy, "allow-workspace-write").subjects = ["*"]


def _m03(policy: PolicySet) -> None:
    _rule(policy, "allow-workspace-read").resources = ["sandbox://*"]


def _m04(policy: PolicySet) -> None:
    _rule(policy, "allow-small-approved-payment").conditions.pop("argument_max", None)


def _m05(policy: PolicySet) -> None:
    _rule(policy, "allow-mock-egress").resources = ["https://*"]


def _m06(policy: PolicySet) -> None:
    _rule(policy, "allow-small-approved-payment").conditions["approval_required"] = False


def _m07(policy: PolicySet) -> None:
    policy.delegation_require_intersection = False
    _rule(policy, "allow-scoped-delegation").conditions[
        "parent_capability_intersection"
    ] = False


def _m08(policy: PolicySet) -> None:
    rule = _rule(policy, "deny-untrusted-provenance")
    rule.conditions["provenance_in"] = ["trusted", "user", "untrusted"]
    rule.effect = "allow"


def _m09(policy: PolicySet) -> None:
    for rule in policy.rules:
        rule.conditions.pop("expires_at_step", None)


def _m10(policy: PolicySet) -> None:
    _rule(policy, "allow-mock-egress").priority = 1500
    _rule(policy, "allow-mock-egress").resources = ["https://*"]


def _m11(policy: PolicySet) -> None:
    policy.fail_closed = False


def _m12(policy: PolicySet) -> None:
    policy.reset_required = False


OPERATORS: tuple[MutationOperator, ...] = (
    MutationOperator("M01", "Decision flip", "Flip a provenance deny into allow.", _m01),
    MutationOperator("M02", "Subject-scope widening", "Broaden a specific writer to any subject.", _m02),
    MutationOperator("M03", "Resource-scope widening", "Broaden workspace scope.", _m03),
    MutationOperator("M04", "Argument constraint deletion", "Remove the payment amount ceiling.", _m04),
    MutationOperator("M05", "Egress allowlist weakening", "Replace mock-host allowlist with wildcard.", _m05),
    MutationOperator("M06", "Approval gate bypass", "Remove human approval requirement.", _m06),
    MutationOperator("M07", "Delegation misbinding", "Remove parent-child capability intersection.", _m07),
    MutationOperator("M08", "Provenance downgrade", "Treat untrusted provenance as permitted.", _m08),
    MutationOperator("M09", "Temporal constraint deletion", "Remove rule expiry constraints.", _m09),
    MutationOperator("M10", "Rule precedence reorder", "Raise permissive wildcard egress above deny.", _m10),
    MutationOperator("M11", "Fail-open exception", "Convert evaluation errors into allow.", _m11),
    MutationOperator("M12", "State-reset omission", "Permit state to persist between runs.", _m12),
)


def operator_by_id(operator_id: str) -> MutationOperator:
    for operator in OPERATORS:
        if operator.operator_id == operator_id:
            return operator
    raise MutationError(f"Unknown mutation operator: {operator_id}")


def generate_first_order_mutants(policy: PolicySet) -> list[PolicySet]:
    mutants: list[PolicySet] = []
    for operator in OPERATORS:
        try:
            mutants.append(operator.apply(policy))
        except (MutationError, PolicyError):
            continue
    return mutants

