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

/**
 * @title AereCardEscrow
 * @notice Pre-auth + capture rail for Bank28 (and future card programs) on AERE.
 *         Settlement node calls preAuth() to lock user funds via EIP-2612 permit
 *         (~3s, 1 block on AERE QBFT). When the card auth resolves, the node
 *         calls capture() with the final amount, which transfers to the
 *         issuer settlement address minus protocol/program fees, and refunds
 *         the residual hold to the user. Expired auths are released by anyone.
 *
 *         This is the "slower path" alternative to AereLightningChannels for
 *         programs that don't run a long-lived channel counterparty.
 */

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

interface IERC2612 {
    function permit(
        address owner, address spender, uint256 value,
        uint256 deadline, uint8 v, bytes32 r, bytes32 s
    ) external;
}

interface IAereIdentity {
    function hasClaim(address identity, bytes32 claimType) external view returns (bool);
}

contract AereCardEscrow {
    // ── Auth state ─────────────────────────────────────────────────
    enum Status { None, Held, Captured, Released }

    struct Auth {
        address user;
        address asset;
        uint128 amount;       // amount on hold (residual after partial captures)
        uint64  expiresAt;
        uint64  programId;    // 1 = Bank28, future programs allocated by owner
        Status  status;
    }

    mapping(bytes32 => Auth) public auths; // authId => Auth

    // ── Programs ───────────────────────────────────────────────────
    struct Program {
        address settlementNode;     // authorized to call preAuth/capture for this program
        address feeRecipient;       // program's own fee bucket (Bank28 treasury)
        uint16  programFeeBps;      // program-charged bps on capture (max 1000)
        bool    active;
    }
    mapping(uint64 => Program) public programs;
    uint64 public nextProgramId = 1;

    // ── Globals ────────────────────────────────────────────────────
    address public owner;
    address public protocolTreasury;
    uint16  public protocolFeeBps = 30; // 30 bps = 0.30% to AereTreasury
    uint16  public constant MAX_PROGRAM_FEE_BPS = 1000;
    uint16  public constant MAX_PROTOCOL_FEE_BPS = 100;
    IAereIdentity public identity;
    bytes32 public requiredClaim; // e.g. keccak256("KYC_TIER_2")

    // ── Events ─────────────────────────────────────────────────────
    event ProgramRegistered(uint64 indexed programId, address settlementNode, address feeRecipient, uint16 feeBps);
    event ProgramUpdated(uint64 indexed programId, address settlementNode, address feeRecipient, uint16 feeBps, bool active);
    event PreAuth(bytes32 indexed authId, uint64 indexed programId, address indexed user, address asset, uint128 amount, uint64 expiresAt);
    event Captured(bytes32 indexed authId, uint128 captured, address recipient, uint128 protocolFee, uint128 programFee, uint128 refunded);
    event Released(bytes32 indexed authId, uint128 refunded);
    event ProtocolFeeUpdated(uint16 bps);
    event RequiredClaimUpdated(bytes32 claim);

    modifier onlyOwner() { require(msg.sender == owner, "owner"); _; }

    constructor(address _treasury, address _identity, bytes32 _requiredClaim) {
        require(_treasury != address(0), "treasury=0");
        owner = msg.sender;
        protocolTreasury = _treasury;
        identity = IAereIdentity(_identity);
        requiredClaim = _requiredClaim;
    }

    // ── Admin ──────────────────────────────────────────────────────
    function registerProgram(address settlementNode, address feeRecipient, uint16 feeBps) external onlyOwner returns (uint64 id) {
        require(settlementNode != address(0) && feeRecipient != address(0), "zero");
        require(feeBps <= MAX_PROGRAM_FEE_BPS, "feeBps");
        id = nextProgramId++;
        programs[id] = Program(settlementNode, feeRecipient, feeBps, true);
        emit ProgramRegistered(id, settlementNode, feeRecipient, feeBps);
    }

    function updateProgram(uint64 id, address settlementNode, address feeRecipient, uint16 feeBps, bool active) external onlyOwner {
        require(programs[id].settlementNode != address(0), "no program");
        require(feeBps <= MAX_PROGRAM_FEE_BPS, "feeBps");
        programs[id] = Program(settlementNode, feeRecipient, feeBps, active);
        emit ProgramUpdated(id, settlementNode, feeRecipient, feeBps, active);
    }

    function setProtocolFeeBps(uint16 bps) external onlyOwner {
        require(bps <= MAX_PROTOCOL_FEE_BPS, "max");
        protocolFeeBps = bps;
        emit ProtocolFeeUpdated(bps);
    }

    function setProtocolTreasury(address t) external onlyOwner { require(t != address(0)); protocolTreasury = t; }
    function setIdentity(address a, bytes32 claim) external onlyOwner { identity = IAereIdentity(a); requiredClaim = claim; emit RequiredClaimUpdated(claim); }
    function transferOwnership(address n) external onlyOwner { require(n != address(0)); owner = n; }

    // ── Pre-auth (settlement node only) ────────────────────────────
    /**
     * @notice Lock user funds for an upcoming card auth.
     * @dev Uses EIP-2612 permit so the user signs off-chain and the settlement
     *      node submits the on-chain tx (sponsored by the program's paymaster).
     *      authId is supplied by the caller — typically keccak256(programId, externalAuthRef)
     *      so the off-chain card system controls the namespace.
     */
    function preAuthWithPermit(
        bytes32 authId,
        uint64  programId,
        address user,
        address asset,
        uint128 amount,
        uint64  expiresAt,
        uint256 permitDeadline,
        uint8 v, bytes32 r, bytes32 s
    ) external {
        Program storage p = programs[programId];
        require(p.active && msg.sender == p.settlementNode, "not node");
        require(auths[authId].status == Status.None, "authId used");
        require(amount > 0 && expiresAt > block.timestamp, "params");
        if (address(identity) != address(0) && requiredClaim != bytes32(0)) {
            require(identity.hasClaim(user, requiredClaim), "not eligible");
        }

        // Pull user funds via permit. Permit failure (e.g. already used) is OK
        // if allowance is already sufficient.
        try IERC2612(asset).permit(user, address(this), amount, permitDeadline, v, r, s) {} catch {}
        require(IERC20(asset).transferFrom(user, address(this), amount), "transferFrom");

        auths[authId] = Auth(user, asset, amount, expiresAt, programId, Status.Held);
        emit PreAuth(authId, programId, user, asset, amount, expiresAt);
    }

    /// @notice Lock funds when the user has already approved this contract (no permit).
    function preAuth(
        bytes32 authId, uint64 programId, address user,
        address asset, uint128 amount, uint64 expiresAt
    ) external {
        Program storage p = programs[programId];
        require(p.active && msg.sender == p.settlementNode, "not node");
        require(auths[authId].status == Status.None, "authId used");
        require(amount > 0 && expiresAt > block.timestamp, "params");
        if (address(identity) != address(0) && requiredClaim != bytes32(0)) {
            require(identity.hasClaim(user, requiredClaim), "not eligible");
        }
        require(IERC20(asset).transferFrom(user, address(this), amount), "transferFrom");
        auths[authId] = Auth(user, asset, amount, expiresAt, programId, Status.Held);
        emit PreAuth(authId, programId, user, asset, amount, expiresAt);
    }

    // ── Capture (settlement node only) ─────────────────────────────
    /**
     * @notice Settle a held auth. Splits funds: protocolFeeBps to AereTreasury,
     *         programFeeBps to program.feeRecipient, captureAmount-fees to
     *         issuerRecipient, residual to the user. Closes the auth.
     */
    function capture(bytes32 authId, uint128 captureAmount, address issuerRecipient) external {
        Auth storage a = auths[authId];
        require(a.status == Status.Held, "not held");
        Program storage p = programs[a.programId];
        require(msg.sender == p.settlementNode, "not node");
        require(captureAmount <= a.amount, "over");
        require(issuerRecipient != address(0), "recipient=0");

        uint128 protoFee = uint128(uint256(captureAmount) * protocolFeeBps / 10_000);
        uint128 progFee  = uint128(uint256(captureAmount) * p.programFeeBps / 10_000);
        require(uint256(protoFee) + progFee < captureAmount, "fees>=capture");

        uint128 toIssuer  = captureAmount - protoFee - progFee;
        uint128 toRefund  = a.amount - captureAmount;

        a.status = Status.Captured;
        a.amount = 0;

        if (protoFee > 0)  require(IERC20(a.asset).transfer(protocolTreasury, protoFee), "t1");
        if (progFee  > 0)  require(IERC20(a.asset).transfer(p.feeRecipient, progFee), "t2");
        if (toIssuer > 0)  require(IERC20(a.asset).transfer(issuerRecipient, toIssuer), "t3");
        if (toRefund > 0)  require(IERC20(a.asset).transfer(a.user, toRefund), "t4");

        emit Captured(authId, captureAmount, issuerRecipient, protoFee, progFee, toRefund);
    }

    // ── Release (anyone, after expiry) ─────────────────────────────
    /// @notice Refund a held auth that wasn't captured before expiry. Permissionless.
    function release(bytes32 authId) external {
        Auth storage a = auths[authId];
        require(a.status == Status.Held, "not held");
        require(block.timestamp >= a.expiresAt, "not expired");
        uint128 amt = a.amount;
        a.status = Status.Released;
        a.amount = 0;
        require(IERC20(a.asset).transfer(a.user, amt), "refund");
        emit Released(authId, amt);
    }

    /// @notice Voluntary release by the program before expiry (e.g. cardholder voided).
    function voidAuth(bytes32 authId) external {
        Auth storage a = auths[authId];
        require(a.status == Status.Held, "not held");
        Program storage p = programs[a.programId];
        require(msg.sender == p.settlementNode, "not node");
        uint128 amt = a.amount;
        a.status = Status.Released;
        a.amount = 0;
        require(IERC20(a.asset).transfer(a.user, amt), "refund");
        emit Released(authId, amt);
    }
}
