// 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 {IAtomicFulfillerRegistry} from "./interfaces/IAtomicFulfillerRegistry.sol";

contract AtomicFulfillerRegistry is AccessControl, IAtomicFulfillerRegistry {
    using SafeERC20 for IERC20;

    bytes32 public constant COORDINATOR_ROLE = keccak256("COORDINATOR_ROLE");
    bytes32 public constant SLASHER_ROLE = keccak256("SLASHER_ROLE");
    bytes32 public constant AUTH_MANAGER_ROLE = keccak256("AUTH_MANAGER_ROLE");

    struct LockedBond {
        address fulfiller;
        bytes32 corridorId;
        uint256 amount;
        bool active;
    }

    IERC20 public immutable bondToken;

    mapping(address => uint256) public totalBond;
    mapping(address => uint256) public lockedBond;
    mapping(address => bool) public fulfillerActive;
    mapping(address => mapping(bytes32 => bool)) public corridorAuthorization;
    mapping(bytes32 => LockedBond) public lockedBonds;

    event BondDeposited(address indexed fulfiller, uint256 amount);
    event BondWithdrawn(address indexed fulfiller, address indexed recipient, uint256 amount);
    event FulfillerActivationSet(address indexed fulfiller, bool active);
    event CorridorAuthorizationSet(address indexed fulfiller, bytes32 indexed corridorId, bool authorized);
    event BondLocked(bytes32 indexed obligationId, address indexed fulfiller, bytes32 indexed corridorId, uint256 amount);
    event BondReleased(bytes32 indexed obligationId, address indexed fulfiller, uint256 amount);
    event BondSlashed(bytes32 indexed obligationId, address indexed fulfiller, address indexed recipient, uint256 amount);

    error InsufficientAvailableBond();
    error UnauthorizedFulfiller();
    error BondAlreadyLocked();
    error BondMissing();

    constructor(address bondToken_, address admin) {
        bondToken = IERC20(bondToken_);
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(AUTH_MANAGER_ROLE, admin);
    }

    function depositBond(uint256 amount) external {
        bondToken.safeTransferFrom(msg.sender, address(this), amount);
        totalBond[msg.sender] += amount;
        emit BondDeposited(msg.sender, amount);
    }

    function withdrawBond(uint256 amount, address recipient) external {
        if (availableBond(msg.sender) < amount) revert InsufficientAvailableBond();
        totalBond[msg.sender] -= amount;
        bondToken.safeTransfer(recipient, amount);
        emit BondWithdrawn(msg.sender, recipient, amount);
    }

    function setFulfillerActive(address fulfiller, bool active) external onlyRole(AUTH_MANAGER_ROLE) {
        fulfillerActive[fulfiller] = active;
        emit FulfillerActivationSet(fulfiller, active);
    }

    function setCorridorAuthorization(address fulfiller, bytes32 corridorId, bool authorized)
        external
        onlyRole(AUTH_MANAGER_ROLE)
    {
        corridorAuthorization[fulfiller][corridorId] = authorized;
        emit CorridorAuthorizationSet(fulfiller, corridorId, authorized);
    }

    function lockBond(bytes32 obligationId, address fulfiller, bytes32 corridorId, uint256 amount)
        external
        onlyRole(COORDINATOR_ROLE)
    {
        if (lockedBonds[obligationId].active) revert BondAlreadyLocked();
        if (!isFulfillerAuthorized(fulfiller, corridorId)) revert UnauthorizedFulfiller();
        if (availableBond(fulfiller) < amount) revert InsufficientAvailableBond();

        lockedBond[fulfiller] += amount;
        lockedBonds[obligationId] = LockedBond({
            fulfiller: fulfiller,
            corridorId: corridorId,
            amount: amount,
            active: true
        });
        emit BondLocked(obligationId, fulfiller, corridorId, amount);
    }

    function releaseBond(bytes32 obligationId)
        external
        onlyRole(COORDINATOR_ROLE)
        returns (uint256 amount)
    {
        LockedBond memory entry = lockedBonds[obligationId];
        if (!entry.active) revert BondMissing();
        lockedBond[entry.fulfiller] -= entry.amount;
        delete lockedBonds[obligationId];
        emit BondReleased(obligationId, entry.fulfiller, entry.amount);
        return entry.amount;
    }

    function slashBond(bytes32 obligationId, address recipient)
        external
        onlyRole(SLASHER_ROLE)
        returns (uint256 amount)
    {
        LockedBond memory entry = lockedBonds[obligationId];
        if (!entry.active) revert BondMissing();
        lockedBond[entry.fulfiller] -= entry.amount;
        totalBond[entry.fulfiller] -= entry.amount;
        delete lockedBonds[obligationId];
        bondToken.safeTransfer(recipient, entry.amount);
        emit BondSlashed(obligationId, entry.fulfiller, recipient, entry.amount);
        return entry.amount;
    }

    function availableBond(address fulfiller) public view returns (uint256) {
        return totalBond[fulfiller] - lockedBond[fulfiller];
    }

    function isFulfillerAuthorized(address fulfiller, bytes32 corridorId) public view returns (bool) {
        return fulfillerActive[fulfiller] && corridorAuthorization[fulfiller][corridorId];
    }

    function canCover(address fulfiller, uint256 amount) external view returns (bool) {
        return availableBond(fulfiller) >= amount;
    }
}
