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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IAtomicLiquidityVault} from "./interfaces/IAtomicLiquidityVault.sol";
import {AtomicTypes} from "./AtomicTypes.sol";

contract AtomicLiquidityVault is AccessControl, IAtomicLiquidityVault {
    using SafeERC20 for IERC20;

    bytes32 public constant COORDINATOR_ROLE = keccak256("COORDINATOR_ROLE");
    bytes32 public constant RECONCILER_ROLE = keccak256("RECONCILER_ROLE");
    bytes32 public constant BUFFER_MANAGER_ROLE = keccak256("BUFFER_MANAGER_ROLE");

    struct StoredCorridorState {
        uint256 totalLiquidity;
        uint256 reservedLiquidity;
        uint256 targetBuffer;
        uint256 settlementBacklog;
    }

    struct Reservation {
        bytes32 corridorId;
        address token;
        uint256 amount;
        bool exists;
        bool fulfilled;
    }

    mapping(bytes32 => mapping(address => StoredCorridorState)) private _corridorState;
    mapping(bytes32 => Reservation) public reservations;

    event CorridorFunded(bytes32 indexed corridorId, address indexed token, address indexed funder, uint256 amount);
    event LiquidityReserved(bytes32 indexed obligationId, bytes32 indexed corridorId, address indexed token, uint256 amount);
    event ReservedLiquidityFulfilled(bytes32 indexed obligationId, address indexed recipient, uint256 amount);
    event ReservationReleased(bytes32 indexed obligationId, uint256 amount);
    event SettlementReconciled(bytes32 indexed corridorId, address indexed token, uint256 amount);
    event TargetBufferSet(bytes32 indexed corridorId, address indexed token, uint256 targetBuffer);

    error ReservationExists();
    error ReservationMissing();
    error ReservationAlreadyFulfilled();
    error InsufficientFreeLiquidity();

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(RECONCILER_ROLE, admin);
        _grantRole(BUFFER_MANAGER_ROLE, admin);
    }

    function fundCorridor(bytes32 corridorId, address token, uint256 amount) external {
        IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        _corridorState[corridorId][token].totalLiquidity += amount;
        emit CorridorFunded(corridorId, token, msg.sender, amount);
    }

    function setTargetBuffer(bytes32 corridorId, address token, uint256 targetBuffer)
        external
        onlyRole(BUFFER_MANAGER_ROLE)
    {
        _corridorState[corridorId][token].targetBuffer = targetBuffer;
        emit TargetBufferSet(corridorId, token, targetBuffer);
    }

    function reserveLiquidity(bytes32 corridorId, address token, bytes32 obligationId, uint256 amount)
        external
        onlyRole(COORDINATOR_ROLE)
    {
        if (reservations[obligationId].exists) revert ReservationExists();
        if (freeLiquidity(corridorId, token) < amount) revert InsufficientFreeLiquidity();

        reservations[obligationId] = Reservation({
            corridorId: corridorId,
            token: token,
            amount: amount,
            exists: true,
            fulfilled: false
        });
        _corridorState[corridorId][token].reservedLiquidity += amount;
        emit LiquidityReserved(obligationId, corridorId, token, amount);
    }

    function fulfillReservedLiquidity(bytes32 obligationId, address recipient)
        external
        onlyRole(COORDINATOR_ROLE)
        returns (uint256 amount)
    {
        Reservation storage reservation = reservations[obligationId];
        if (!reservation.exists) revert ReservationMissing();
        if (reservation.fulfilled) revert ReservationAlreadyFulfilled();

        reservation.fulfilled = true;
        StoredCorridorState storage state = _corridorState[reservation.corridorId][reservation.token];
        state.reservedLiquidity -= reservation.amount;
        state.totalLiquidity -= reservation.amount;
        state.settlementBacklog += reservation.amount;

        IERC20(reservation.token).safeTransfer(recipient, reservation.amount);
        emit ReservedLiquidityFulfilled(obligationId, recipient, reservation.amount);
        return reservation.amount;
    }

    function releaseReservation(bytes32 obligationId)
        external
        onlyRole(COORDINATOR_ROLE)
        returns (uint256 amount)
    {
        Reservation storage reservation = reservations[obligationId];
        if (!reservation.exists) revert ReservationMissing();
        if (reservation.fulfilled) revert ReservationAlreadyFulfilled();

        amount = reservation.amount;
        _corridorState[reservation.corridorId][reservation.token].reservedLiquidity -= amount;
        delete reservations[obligationId];
        emit ReservationReleased(obligationId, amount);
    }

    function reconcileSettlement(bytes32 corridorId, address token, uint256 amount, address from)
        external
        onlyRole(RECONCILER_ROLE)
    {
        IERC20(token).safeTransferFrom(from, address(this), amount);
        StoredCorridorState storage state = _corridorState[corridorId][token];
        state.totalLiquidity += amount;
        uint256 backlog = state.settlementBacklog;
        state.settlementBacklog = amount >= backlog ? 0 : backlog - amount;
        emit SettlementReconciled(corridorId, token, amount);
    }

    function getCorridorLiquidityState(bytes32 corridorId, address token)
        external
        view
        returns (AtomicTypes.CorridorLiquidityState memory state)
    {
        StoredCorridorState memory stored = _corridorState[corridorId][token];
        state = AtomicTypes.CorridorLiquidityState({
            totalLiquidity: stored.totalLiquidity,
            reservedLiquidity: stored.reservedLiquidity,
            freeLiquidity: stored.totalLiquidity - stored.reservedLiquidity,
            targetBuffer: stored.targetBuffer,
            settlementBacklog: stored.settlementBacklog
        });
    }

    function freeLiquidity(bytes32 corridorId, address token) public view returns (uint256) {
        StoredCorridorState memory state = _corridorState[corridorId][token];
        return state.totalLiquidity - state.reservedLiquidity;
    }
}
