// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

import {Test, console} from "forge-std/Test.sol";
import {AggregatedVerifier} from "../src/AggregatedVerifier.sol";

contract AggregatedVerifierTest is Test {
    AggregatedVerifier public verifier;

    // BN254 G1 generator
    uint256 constant G1_X = 1;
    uint256 constant G1_Y = 2;

    function setUp() public {
        verifier = new AggregatedVerifier();
    }

    // ============ Basic Deployment Tests ============

    function test_DeploymentSucceeds() public view {
        assertTrue(address(verifier) != address(0));
    }

    // ============ Proof Verification Tests ============

    function test_VerifyWithValidProof() public view {
        // Create a valid proof with proper G1 encoding
        // Format: adjusted_commitment (64 bytes = 2x32) + combined_quotient (64 bytes) + padding
        bytes memory proof = new bytes(132);
        
        // Set adjusted_commitment to G1 generator (1, 2)
        // x at offset 0, y at offset 32
        assembly {
            mstore(add(proof, 32), G1_X)   // x = 1 at bytes 0-31
            mstore(add(proof, 64), G1_Y)   // y = 2 at bytes 32-63
        }
        
        // Set combined_quotient to G1 generator (1, 2)
        // x at offset 64, y at offset 96
        assembly {
            mstore(add(proof, 96), G1_X)   // x = 1 at bytes 64-95
            mstore(add(proof, 128), G1_Y)  // y = 2 at bytes 96-127
        }

        uint256[] memory publicInputs = new uint256[](1);
        publicInputs[0] = 42;

        bool success = verifier.verify(proof, publicInputs);
        assertTrue(success, "Valid proof should verify");
    }

    function test_VerifyRejectsShortProof() public {
        bytes memory shortProof = new bytes(50);
        uint256[] memory publicInputs = new uint256[](0);

        vm.expectRevert(AggregatedVerifier.InvalidProofLength.selector);
        verifier.verify(shortProof, publicInputs);
    }

    function test_VerifyEmptyPublicInputs() public view {
        bytes memory proof = new bytes(132);
        
        // Set valid G1 points with 64-byte format
        assembly {
            mstore(add(proof, 32), G1_X)
            mstore(add(proof, 64), G1_Y)
            mstore(add(proof, 96), G1_X)
            mstore(add(proof, 128), G1_Y)
        }

        uint256[] memory publicInputs = new uint256[](0);
        bool success = verifier.verify(proof, publicInputs);
        assertTrue(success, "Empty public inputs should be valid");
    }

    // ============ Groth16 Interface Tests ============

    function test_VerifyGroth16WithGeneratorPoints() public {
        uint256[2] memory a = [G1_X, G1_Y];
        uint256[2][2] memory b = [
            [uint256(0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed),
             uint256(0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2)],
            [uint256(0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa),
             uint256(0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b)]
        ];
        uint256[2] memory c = [G1_X, G1_Y];
        uint256[] memory publicInputs = new uint256[](1);
        publicInputs[0] = 1;

        // This tests the pairing precompile is called correctly
        bool success = verifier.verifyGroth16(a, b, c, publicInputs);
        console.log("Groth16 verification result:", success);
    }

    function test_VerifyGroth16RejectsInvalidPoint() public view {
        // Point not on curve (random coordinates)
        uint256[2] memory a = [uint256(123), uint256(456)];
        uint256[2][2] memory b = [
            [uint256(0), uint256(0)],
            [uint256(0), uint256(0)]
        ];
        uint256[2] memory c = [G1_X, G1_Y];
        uint256[] memory publicInputs = new uint256[](0);

        bool success = verifier.verifyGroth16(a, b, c, publicInputs);
        assertFalse(success, "Invalid point should fail verification");
    }

    // ============ Curve Validation Tests ============

    function test_GeneratorIsOnCurve() public view {
        // G1 generator (1, 2) satisfies y^2 = x^3 + 3 mod P
        // 2^2 = 4
        // 1^3 + 3 = 4
        // 4 == 4 ✓
        bytes memory proof = new bytes(132);
        assembly {
            mstore(add(proof, 32), G1_X)
            mstore(add(proof, 64), G1_Y)
            mstore(add(proof, 96), G1_X)
            mstore(add(proof, 128), G1_Y)
        }

        uint256[] memory publicInputs = new uint256[](0);
        bool success = verifier.verify(proof, publicInputs);
        assertTrue(success, "Generator point should be on curve");
    }

    // ============ Integration Test with Real Proof Data ============

    /// @notice Test with proof data format matching samaharam output
    function test_VerifyRealProofFormat() public {
        // Simulated aggregated proof from samaharam
        // adjusted_commitment: 48 bytes (G1 compressed)
        // combined_quotient: 48 bytes (G1 compressed)
        // count: 4 bytes (u32 little-endian)
        bytes memory proof = abi.encodePacked(
            bytes32(G1_X), bytes16(0),  // adjusted_commitment x (48 bytes)
            bytes32(G1_Y), bytes16(0),  // adjusted_commitment y (48 bytes)  
            uint32(3)                    // count = 3 proofs aggregated
        );

        // Pad to minimum 100 bytes
        bytes memory paddedProof = new bytes(100);
        for (uint i = 0; i < proof.length && i < 100; i++) {
            paddedProof[i] = proof[i];
        }

        uint256[] memory publicInputs = new uint256[](3);
        publicInputs[0] = 77;   // 7 * 11
        publicInputs[1] = 221;  // 13 * 17
        publicInputs[2] = 42;   // Example

        bool success = verifier.verify(paddedProof, publicInputs);
        console.log("Real proof format verification:", success);
    }
}
