// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../../contracts/registry/UniversalAssetRegistry.sol";
import "../../contracts/governance/GovernanceController.sol";
import "../../contracts/tokens/CompliantFiatTokenV2.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract JurisdictionalGovernanceTest is Test {
    UniversalAssetRegistry internal registry;
    GovernanceController internal governance;
    CompliantFiatTokenV2 internal token;

    address internal admin;
    address internal authority;
    address internal transitionAuthority;

    function setUp() public {
        admin = makeAddr("admin");
        authority = makeAddr("authority");
        transitionAuthority = makeAddr("transitionAuthority");

        vm.startPrank(admin);

        UniversalAssetRegistry registryImpl = new UniversalAssetRegistry();
        bytes memory registryInit = abi.encodeCall(UniversalAssetRegistry.initialize, (admin));
        ERC1967Proxy registryProxy = new ERC1967Proxy(address(registryImpl), registryInit);
        registry = UniversalAssetRegistry(address(registryProxy));

        GovernanceController governanceImpl = new GovernanceController();
        bytes memory governanceInit = abi.encodeCall(GovernanceController.initialize, (address(registry), admin));
        ERC1967Proxy governanceProxy = new ERC1967Proxy(address(governanceImpl), governanceInit);
        governance = GovernanceController(address(governanceProxy));

        token = new CompliantFiatTokenV2(
            "Euro Coin (Compliant V2)",
            "cEURC",
            6,
            "EUR",
            "2",
            admin,
            admin,
            0,
            true
        );

        registry.setGovernanceController(address(governance));
        token.setGovernanceController(address(governance));
        token.emergencySetGovernanceMetadata(
            token.governanceProfileId(),
            token.supervisionProfileId(),
            token.storageNamespace(),
            "EU",
            address(0),
            true,
            true,
            14 days
        );
        registry.emergencySetJurisdictionProfile(
            "EU",
            true,
            true,
            true,
            14 days,
            "ipfs://eu-supervision",
            keccak256("eu-policy")
        );
        registry.emergencySetJurisdictionProfile(
            "SG",
            true,
            true,
            true,
            21 days,
            "ipfs://sg-supervision",
            keccak256("sg-policy")
        );
        registry.emergencySetJurisdictionAuthority("EU", authority, true, true, true, true, true);
        registry.emergencySetJurisdictionAuthority("SG", transitionAuthority, true, true, true, true, true);
        registry.addValidator(authority);
        registry.registerGRUCompliantAsset(address(token), "Euro Coin (Compliant V2)", "cEURC", 6, "EU");

        vm.stopPrank();
    }

    function testRegistryPullsGovernanceMetadataFromTokenAndJurisdiction() public view {
        UniversalAssetRegistry.UniversalAsset memory asset = registry.getAsset(address(token));

        assertEq(asset.jurisdiction, "EU");
        assertEq(asset.assetId, token.assetId());
        assertEq(asset.assetVersionId, token.assetVersionId());
        assertEq(asset.governanceProfileId, token.governanceProfileId());
        assertEq(asset.supervisionProfileId, token.supervisionProfileId());
        assertEq(asset.storageNamespace, token.storageNamespace());
        assertTrue(asset.supervisionRequired);
        assertTrue(asset.governmentApprovalRequired);
        assertEq(asset.minimumUpgradeNoticePeriod, 14 days);
    }

    function testQueueRequiresJurisdictionApprovalAndUsesJurisdictionNoticePeriod() public {
        address[] memory targets = new address[](1);
        uint256[] memory values = new uint256[](1);
        bytes[] memory calldatas = new bytes[](1);
        bytes32 updatedGovernanceProfile = keccak256("eu-governance-v2");

        targets[0] = address(token);
        values[0] = 0;
        calldatas[0] = abi.encodeWithSelector(
            CompliantFiatTokenV2.setGovernanceProfileId.selector,
            updatedGovernanceProfile
        );

        vm.prank(admin);
        uint256 proposalId = governance.proposeForAsset(
            address(token),
            targets,
            values,
            calldatas,
            "Update EU governance metadata",
            GovernanceController.GovernanceMode.TimelockShort
        );
        assertEq(governance.proposalAssets(proposalId), address(token));

        vm.roll(block.number + governance.votingDelay() + 1);

        vm.prank(authority);
        governance.castVote(proposalId, 1);

        vm.roll(block.number + governance.votingPeriod() + 1);

        vm.expectRevert("Primary jurisdiction approval required");
        governance.queue(proposalId);

        vm.prank(authority);
        governance.approveProposalJurisdiction(proposalId);

        uint256 beforeQueue = block.timestamp;
        governance.queue(proposalId);

        uint256 eta = governance.getProposalEta(proposalId);
        assertGe(eta, beforeQueue + 14 days);
        assertEq(governance.proposalJurisdictionApprovalCount(proposalId), 1);
        assertEq(governance.proposalLastJurisdictionApprover(proposalId), authority);

        vm.warp(eta);
        governance.execute(proposalId);

        assertEq(token.governanceProfileId(), updatedGovernanceProfile);
    }

    function testAssetScopedProposalRejectsRegistryCallsForAnotherAsset() public {
        address[] memory targets = new address[](1);
        uint256[] memory values = new uint256[](1);
        bytes[] memory calldatas = new bytes[](1);

        targets[0] = address(registry);
        values[0] = 0;
        calldatas[0] = abi.encodeWithSelector(
            UniversalAssetRegistry.syncAssetMetadataFromToken.selector,
            address(0xBEEF)
        );

        vm.prank(admin);
        vm.expectRevert("Registry asset mismatch");
        governance.proposeForAsset(
            address(token),
            targets,
            values,
            calldatas,
            "Attempt mismatched registry sync",
            GovernanceController.GovernanceMode.TimelockShort
        );
    }

    function testRegistryMetadataRequiresGovernanceButEmergencyPathRemainsAvailable() public {
        vm.prank(admin);
        vm.expectRevert();
        registry.setAssetGovernanceProfile(
            address(token),
            keccak256("blocked-gov-profile"),
            keccak256("blocked-sup-profile"),
            keccak256("blocked-storage"),
            address(0x1234),
            false,
            true,
            true,
            21 days,
            "ipfs://blocked-disclosure",
            "ipfs://blocked-reporting"
        );

        vm.prank(admin);
        registry.emergencySetAssetGovernanceProfile(
            address(token),
            keccak256("manual-gov-profile"),
            keccak256("manual-sup-profile"),
            keccak256("manual-storage"),
            address(0x1234),
            false,
            true,
            true,
            21 days,
            "ipfs://manual-disclosure",
            "ipfs://manual-reporting"
        );

        UniversalAssetRegistry.UniversalAsset memory asset = registry.getAsset(address(token));
        assertEq(asset.governanceProfileId, keccak256("manual-gov-profile"));
        assertEq(asset.supervisionProfileId, keccak256("manual-sup-profile"));
        assertEq(asset.storageNamespace, keccak256("manual-storage"));
        assertEq(asset.canonicalUnderlyingAsset, address(0x1234));
        assertEq(asset.regulatoryDisclosureURI, "ipfs://manual-disclosure");
        assertEq(asset.reportingURI, "ipfs://manual-reporting");
        assertEq(asset.minimumUpgradeNoticePeriod, 21 days);
    }

    function testJurisdictionProfileCanBeManagedThroughAssetScopedGovernance() public {
        address[] memory targets = new address[](1);
        uint256[] memory values = new uint256[](1);
        bytes[] memory calldatas = new bytes[](1);

        targets[0] = address(registry);
        values[0] = 0;
        calldatas[0] = abi.encodeWithSelector(
            UniversalAssetRegistry.setDerivedJurisdictionProfile.selector,
            address(token),
            true,
            true,
            true,
            30 days,
            "ipfs://eu-supervision-v2",
            keccak256("eu-policy-v2")
        );

        vm.prank(admin);
        uint256 proposalId = governance.proposeForAsset(
            address(token),
            targets,
            values,
            calldatas,
            "Update derived EU jurisdiction profile",
            GovernanceController.GovernanceMode.TimelockShort
        );

        vm.roll(block.number + governance.votingDelay() + 1);
        vm.prank(authority);
        governance.castVote(proposalId, 1);
        vm.roll(block.number + governance.votingPeriod() + 1);

        vm.prank(authority);
        governance.approveProposalJurisdiction(proposalId);

        governance.queue(proposalId);
        vm.warp(governance.getProposalEta(proposalId));
        governance.execute(proposalId);

        UniversalAssetRegistry.JurisdictionProfile memory profile = registry.getJurisdictionProfile("EU");
        assertEq(profile.minimumUpgradeNoticePeriod, 30 days);
        assertEq(profile.supervisionURI, "ipfs://eu-supervision-v2");
        assertEq(profile.policyHash, keccak256("eu-policy-v2"));
    }

    function testJurisdictionTransitionRequiresCurrentAndDestinationApprovals() public {
        address[] memory targets = new address[](2);
        uint256[] memory values = new uint256[](2);
        bytes[] memory calldatas = new bytes[](2);

        targets[0] = address(token);
        values[0] = 0;
        calldatas[0] = abi.encodeWithSelector(
            CompliantFiatTokenV2.setPrimaryJurisdiction.selector,
            "SG"
        );

        targets[1] = address(registry);
        values[1] = 0;
        calldatas[1] = abi.encodeWithSelector(
            UniversalAssetRegistry.syncAssetMetadataFromToken.selector,
            address(token)
        );

        vm.prank(admin);
        uint256 proposalId = governance.proposeForAsset(
            address(token),
            targets,
            values,
            calldatas,
            "Move asset jurisdiction to SG",
            GovernanceController.GovernanceMode.TimelockShort
        );

        assertEq(governance.proposalTransitionJurisdictionIds(proposalId), registry.jurisdictionIdFor("SG"));

        vm.roll(block.number + governance.votingDelay() + 1);
        vm.prank(authority);
        governance.castVote(proposalId, 1);
        vm.roll(block.number + governance.votingPeriod() + 1);

        vm.prank(authority);
        governance.approveProposalJurisdiction(proposalId);

        vm.expectRevert("Transition jurisdiction approval required");
        governance.queue(proposalId);

        vm.prank(transitionAuthority);
        governance.approveProposalJurisdiction(proposalId);

        uint256 beforeQueue = block.timestamp;
        governance.queue(proposalId);

        assertTrue(governance.hasJurisdictionApproval(proposalId, authority));
        assertTrue(governance.hasTransitionJurisdictionApproval(proposalId, transitionAuthority));
        assertEq(governance.proposalTransitionJurisdictionApprovalCount(proposalId), 1);
        assertEq(governance.proposalLastTransitionJurisdictionApprover(proposalId), transitionAuthority);
        assertGe(governance.getProposalEta(proposalId), beforeQueue + 21 days);

        vm.warp(governance.getProposalEta(proposalId));
        governance.execute(proposalId);

        UniversalAssetRegistry.UniversalAsset memory asset = registry.getAsset(address(token));
        assertEq(token.primaryJurisdiction(), "SG");
        assertEq(asset.jurisdiction, "SG");
        assertEq(registry.getAssetJurisdictionId(address(token)), registry.jurisdictionIdFor("SG"));
    }
}
