"""Regression tests for the offline Base governance construction-package generator."""
from __future__ import annotations

import copy
import hashlib
import importlib.util
import json
import sys
import unittest
from pathlib import Path

from Crypto.Hash import keccak
from eth_abi import encode as eth_abi_encode

MODULE_PATH = Path(__file__).with_name("safe_payload_generator.py")
SPEC = importlib.util.spec_from_file_location("safe_payload_generator", MODULE_PATH)
assert SPEC and SPEC.loader
generator = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = generator
SPEC.loader.exec_module(generator)

GOVERNANCE = "0x1111111111111111111111111111111111111111"
SAFE_SIGNER = "0x2222222222222222222222222222222222222222"
COORDINATOR = "0x3333333333333333333333333333333333333333"
DESIGN_PARTNER = "0x4444444444444444444444444444444444444444"
PARTNERSHIP_FACTORY = "0x5555555555555555555555555555555555555555"
ACCOUNTABILITY_FACTORY = "0x6666666666666666666666666666666666666666"
CONTROLLER = "0x7777777777777777777777777777777777777777"


def selector(signature: str) -> str:
    digest = keccak.new(digest_bits=256)
    digest.update(signature.encode("ascii"))
    return "0x" + digest.digest()[:4].hex()


def finalization_manifest() -> dict:
    value = json.loads((MODULE_PATH.parents[1] / "pilot" / "base_v3_deployment_manifest.json").read_text())
    value["status"] = "finalization-candidate"
    value["provenance"] = {
        "sourceCommit": "a" * 40, "sourceTree": "b" * 40, "contractsTree": "c" * 40,
        "evidenceCommit": "d" * 40, "evidenceTree": "e" * 40, "sourceArchiveSha256": "f" * 64,
        "evidenceArchiveSha256": "1" * 64, "reviewerBundleSha256": "2" * 64, "validationArchiveSha256": "3" * 64,
    }
    material = copy.deepcopy(value)
    material.pop("integrity")
    value["integrity"]["manifestPayloadSha256"] = hashlib.sha256(
        json.dumps(material, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
    ).hexdigest()
    return value


def sample_config() -> generator.BuildConfig:
    return generator.BuildConfig(
        chain="base", governance=GOVERNANCE, safe_signer=SAFE_SIGNER, factory_coordinator=COORDINATOR,
        controller=CONTROLLER, design_partner=DESIGN_PARTNER, partnership_factory=PARTNERSHIP_FACTORY,
        accountability_factory=ACCOUNTABILITY_FACTORY, deployment_manifest=finalization_manifest(), created_at=1_725_000_000,
    )


class GovernanceProposalGeneratorTests(unittest.TestCase):
    def test_complete_ordered_construction_sequence_has_exact_manifest_caps(self) -> None:
        batch = generator.build(sample_config())
        proposals = batch["proposals"]
        self.assertEqual(
            [p["governedContractMethod"]["name"] for p in proposals],
            ["setModule", "setDesignPartner", "setCaps", "configureFactories", "setDeployEnabled", "setDeployAllowed", "deployPartnership", "deployAccountability"],
        )
        self.assertEqual([p["target"] for p in proposals[:3]], [CONTROLLER] * 3)
        self.assertEqual(proposals[3]["target"], COORDINATOR)
        self.assertEqual(
            proposals[2]["calldata"],
            selector("setCaps(uint256,uint256)")
            + eth_abi_encode(["uint256", "uint256"], [400_000_000_000_000_000, 200_000_000_000_000_000]).hex(),
        )
        self.assertEqual(
            proposals[3]["calldata"],
            selector("configureFactories(address,address)")
            + eth_abi_encode(["address", "address"], [PARTNERSHIP_FACTORY, ACCOUNTABILITY_FACTORY]).hex(),
        )
        self.assertEqual(proposals[6]["calldata"], selector("deployPartnership(address,address)") + eth_abi_encode(["address", "address"], [DESIGN_PARTNER, generator.ZERO_ADDRESS]).hex())
        self.assertEqual(proposals[7]["calldata"], selector("deployAccountability(address)") + eth_abi_encode(["address"], [DESIGN_PARTNER]).hex())
        self.assertTrue(all(p["proposalTransaction"]["to"] == GOVERNANCE for p in proposals))
        self.assertFalse(batch["postDeploymentFollowUp"]["constructionBatchContainsSetWriteEnabled"])
        self.assertIsNone(batch["postDeploymentFollowUp"]["setWriteEnabledCalldata"])
        self.assertIn("child-runtime-codehashes-match-constructor-pinned-values", batch["postDeploymentFollowUp"]["requiredExecutionReceipts"])

    def test_manifest_is_sole_cap_source_and_rejects_mismatch_or_above_ceiling(self) -> None:
        mismatched = finalization_manifest()
        mismatched["caps"]["globalWei"] = "400000000000000001"
        with self.assertRaisesRegex(generator.ValidationError, "deployment manifest failed validation"):
            generator.build(generator.BuildConfig(**{**sample_config().__dict__, "deployment_manifest": mismatched}))
        above_ceiling = finalization_manifest()
        above_ceiling["caps"]["perPositionWei"] = "200000000000000001"
        material = copy.deepcopy(above_ceiling)
        material.pop("integrity")
        above_ceiling["integrity"]["manifestPayloadSha256"] = hashlib.sha256(
            json.dumps(material, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
        ).hexdigest()
        with self.assertRaisesRegex(generator.ValidationError, "deployment manifest failed validation"):
            generator.build(generator.BuildConfig(**{**sample_config().__dict__, "deployment_manifest": above_ceiling}))

    def test_requires_finalization_candidate_and_rejects_bad_addresses(self) -> None:
        draft = finalization_manifest()
        draft["status"] = "pre-deployment-review"
        draft["provenance"] = {key: None for key in draft["provenance"]}
        material = copy.deepcopy(draft)
        material.pop("integrity")
        draft["integrity"]["manifestPayloadSha256"] = hashlib.sha256(
            json.dumps(material, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
        ).hexdigest()
        with self.assertRaisesRegex(generator.ValidationError, "finalization-candidate"):
            generator.build(generator.BuildConfig(**{**sample_config().__dict__, "deployment_manifest": draft}))
        with self.assertRaisesRegex(generator.ValidationError, "BasePilotAccessControllerV3"):
            generator.build(generator.BuildConfig(**{**sample_config().__dict__, "controller": generator.ZERO_ADDRESS}))


if __name__ == "__main__":
    unittest.main()
