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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {AtomicTypes} from "./AtomicTypes.sol";
import {IAtomicBridgeCoordinator} from "./interfaces/IAtomicBridgeCoordinator.sol";
import {IAtomicLiquidityVault} from "./interfaces/IAtomicLiquidityVault.sol";
import {IAtomicFulfillerRegistry} from "./interfaces/IAtomicFulfillerRegistry.sol";
import {AtomicObligationEscrow} from "./AtomicObligationEscrow.sol";
import {AtomicSettlementRouter} from "./AtomicSettlementRouter.sol";
import {AtomicFeePolicy} from "./AtomicFeePolicy.sol";
import {AtomicSlashingManager} from "./AtomicSlashingManager.sol";

contract AtomicBridgeCoordinator is AccessControl, ReentrancyGuard, IAtomicBridgeCoordinator {
    bytes32 public constant CORRIDOR_MANAGER_ROLE = keccak256("CORRIDOR_MANAGER_ROLE");
    bytes32 public constant SETTLEMENT_MANAGER_ROLE = keccak256("SETTLEMENT_MANAGER_ROLE");

    IAtomicLiquidityVault public immutable liquidityVault;
    IAtomicFulfillerRegistry public immutable fulfillerRegistry;
    AtomicObligationEscrow public immutable obligationEscrow;
    AtomicSettlementRouter public immutable settlementRouter;
    AtomicFeePolicy public immutable feePolicy;
    AtomicSlashingManager public immutable slashingManager;
    address public immutable protocolTreasury;

    struct CreateIntentParams {
        uint64 sourceChain;
        uint64 destinationChain;
        address assetIn;
        address assetOut;
        uint256 amountIn;
        uint256 minAmountOut;
        address recipient;
        uint256 deadline;
        bytes32 routeId;
    }

    uint256 public intentNonce;
    mapping(bytes32 => AtomicTypes.CorridorConfig) private _corridors;
    mapping(bytes32 => AtomicTypes.AtomicIntent) private _intents;
    mapping(bytes32 => AtomicTypes.AtomicCommitment) private _commitments;
    mapping(bytes32 => AtomicTypes.AtomicObligation) private _obligations;

    event CorridorConfigured(bytes32 indexed corridorId, address indexed assetIn, address indexed assetOut);
    event IntentCreated(bytes32 indexed obligationId, bytes32 indexed corridorId, bytes32 indexed intentId, address sender);
    event CommitmentAccepted(bytes32 indexed obligationId, address indexed fulfiller, uint256 bondAmount);
    event SettlementInitiated(bytes32 indexed obligationId, bytes32 indexed settlementId, bytes32 indexed settlementMode);
    event SettlementConfirmed(bytes32 indexed obligationId, uint256 replenishAmount);
    event IntentRefunded(bytes32 indexed obligationId, uint256 refundedAmount);
    event CorridorDegraded(bytes32 indexed corridorId, bytes32 indexed obligationId, bytes32 reason);

    error CorridorDisabled();
    error CorridorDegradedError();
    error InvalidCorridor();
    error InvalidDeadline();
    error MaxNotionalExceeded();
    error ReservedLiquidityLimitExceeded();
    error SettlementBacklogExceeded();
    error InvalidStatus();
    error DeadlineNotReached();
    error SettlementTimeoutNotReached();
    error MinimumReplenishNotMet();

    constructor(
        address liquidityVault_,
        address fulfillerRegistry_,
        address obligationEscrow_,
        address settlementRouter_,
        address feePolicy_,
        address slashingManager_,
        address protocolTreasury_,
        address admin
    ) {
        liquidityVault = IAtomicLiquidityVault(liquidityVault_);
        fulfillerRegistry = IAtomicFulfillerRegistry(fulfillerRegistry_);
        obligationEscrow = AtomicObligationEscrow(obligationEscrow_);
        settlementRouter = AtomicSettlementRouter(settlementRouter_);
        feePolicy = AtomicFeePolicy(feePolicy_);
        slashingManager = AtomicSlashingManager(slashingManager_);
        protocolTreasury = protocolTreasury_;
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(CORRIDOR_MANAGER_ROLE, admin);
        _grantRole(SETTLEMENT_MANAGER_ROLE, admin);
    }

    function getCorridorId(
        uint64 sourceChain,
        uint64 destinationChain,
        address assetIn,
        address assetOut
    ) public pure returns (bytes32) {
        return keccak256(abi.encode(sourceChain, destinationChain, assetIn, assetOut));
    }

    function configureCorridor(AtomicTypes.CorridorConfig calldata cfg) external onlyRole(CORRIDOR_MANAGER_ROLE) {
        bytes32 corridorId = getCorridorId(cfg.sourceChain, cfg.destinationChain, cfg.assetIn, cfg.assetOut);
        _corridors[corridorId] = cfg;
        emit CorridorConfigured(corridorId, cfg.assetIn, cfg.assetOut);
    }

    function setCorridorDegraded(bytes32 corridorId, bool degraded) external onlyRole(CORRIDOR_MANAGER_ROLE) {
        _corridors[corridorId].degraded = degraded;
    }

    function createIntent(CreateIntentParams calldata p) external nonReentrant returns (bytes32 obligationId) {
        bytes32 corridorId = getCorridorId(p.sourceChain, p.destinationChain, p.assetIn, p.assetOut);
        AtomicTypes.CorridorConfig memory cfg = _corridors[corridorId];
        if (!cfg.enabled) revert CorridorDisabled();
        if (cfg.degraded) revert CorridorDegradedError();
        if (cfg.assetIn != p.assetIn || cfg.assetOut != p.assetOut) revert InvalidCorridor();
        if (p.deadline <= block.timestamp || p.deadline > block.timestamp + cfg.fulfilmentTimeout) revert InvalidDeadline();
        if (p.amountIn > cfg.maxNotional) revert MaxNotionalExceeded();

        AtomicTypes.CorridorLiquidityState memory state = liquidityVault.getCorridorLiquidityState(corridorId, p.assetOut);
        if (p.minAmountOut > state.freeLiquidity) revert ReservedLiquidityLimitExceeded();
        if (state.totalLiquidity > 0) {
            uint256 nextReserved = state.reservedLiquidity + p.minAmountOut;
            if ((nextReserved * 10_000) / state.totalLiquidity > cfg.maxReservedBps) {
                revert ReservedLiquidityLimitExceeded();
            }
        }
        if (state.settlementBacklog > cfg.maxSettlementBacklog) revert SettlementBacklogExceeded();

        bytes32 intentId = keccak256(
            abi.encode(
                block.chainid,
                msg.sender,
                ++intentNonce,
                corridorId,
                p.amountIn,
                p.minAmountOut,
                p.deadline,
                p.routeId
            )
        );
        obligationId = keccak256(abi.encode(intentId, p.recipient));

        _intents[obligationId] = AtomicTypes.AtomicIntent({
            sourceChain: p.sourceChain,
            destinationChain: p.destinationChain,
            assetIn: p.assetIn,
            assetOut: p.assetOut,
            amountIn: p.amountIn,
            minAmountOut: p.minAmountOut,
            recipient: p.recipient,
            deadline: p.deadline,
            routeId: p.routeId,
            intentId: intentId
        });
        _obligations[obligationId] = AtomicTypes.AtomicObligation({
            obligationId: obligationId,
            intentId: intentId,
            sourceEscrow: p.amountIn,
            destinationReserve: p.minAmountOut,
            fulfiller: address(0),
            status: AtomicTypes.ObligationStatus.IntentCreated,
            settlementInitiatedAt: 0
        });

        obligationEscrow.escrowFunds(obligationId, p.assetIn, msg.sender, p.amountIn);
        liquidityVault.reserveLiquidity(corridorId, p.assetOut, obligationId, p.minAmountOut);

        emit IntentCreated(obligationId, corridorId, intentId, msg.sender);
    }

    function submitCommitment(bytes32 obligationId, bytes32 settlementMode) external nonReentrant {
        AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
        AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId];
        if (obligation.status != AtomicTypes.ObligationStatus.IntentCreated) revert InvalidStatus();
        if (block.timestamp > intent.deadline) revert DeadlineNotReached();

        bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
        bytes32 mode = settlementMode == bytes32(0) ? _corridors[corridorId].defaultSettlementMode : settlementMode;
        uint256 requiredBond = feePolicy.requiredBond(corridorId, obligation.destinationReserve);
        fulfillerRegistry.lockBond(obligationId, msg.sender, corridorId, requiredBond);
        liquidityVault.fulfillReservedLiquidity(obligationId, intent.recipient);

        _commitments[obligationId] = AtomicTypes.AtomicCommitment({
            intentId: intent.intentId,
            fulfiller: msg.sender,
            reservedLiquidity: obligation.destinationReserve,
            bondAmount: requiredBond,
            expiry: block.timestamp + _corridors[corridorId].settlementTimeout,
            settlementMode: mode
        });
        obligation.fulfiller = msg.sender;
        obligation.status = AtomicTypes.ObligationStatus.Fulfilled;

        emit CommitmentAccepted(obligationId, msg.sender, requiredBond);
    }

    function initiateSettlement(bytes32 obligationId, bytes calldata settlementData)
        external
        payable
        nonReentrant
        onlyRole(SETTLEMENT_MANAGER_ROLE)
        returns (bytes32 settlementId)
    {
        AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
        AtomicTypes.AtomicCommitment memory commitment = _commitments[obligationId];
        AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId];
        if (obligation.status != AtomicTypes.ObligationStatus.Fulfilled) revert InvalidStatus();

        bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
        uint256 settlementAmount = _releaseEscrowForSettlement(
            obligationId,
            corridorId,
            intent.amountIn,
            commitment.fulfiller
        );
        settlementId = _executeSettlement(
            obligationId,
            commitment.settlementMode,
            intent.assetIn,
            settlementAmount,
            intent.recipient,
            settlementData
        );

        obligation.status = AtomicTypes.ObligationStatus.SettlementPending;
        obligation.settlementInitiatedAt = block.timestamp;
        emit SettlementInitiated(obligationId, settlementId, commitment.settlementMode);
    }

    function _releaseEscrowForSettlement(
        bytes32 obligationId,
        bytes32 corridorId,
        uint256 amountIn,
        address fulfiller
    ) internal returns (uint256 settlementAmount) {
        (uint256 fulfillerFee, uint256 protocolFee) = feePolicy.quoteFees(corridorId, amountIn);
        settlementAmount = amountIn - fulfillerFee - protocolFee;

        if (protocolFee > 0) {
            obligationEscrow.release(obligationId, protocolTreasury, protocolFee);
        }
        if (fulfillerFee > 0) {
            obligationEscrow.release(obligationId, fulfiller, fulfillerFee);
        }
        obligationEscrow.release(obligationId, address(settlementRouter), settlementAmount);
    }

    function _executeSettlement(
        bytes32 obligationId,
        bytes32 settlementMode,
        address assetIn,
        uint256 settlementAmount,
        address recipient,
        bytes calldata settlementData
    ) internal returns (bytes32 settlementId) {
        settlementId = settlementRouter.executeSettlement{value: msg.value}(
            obligationId,
            settlementMode,
            assetIn,
            settlementAmount,
            recipient,
            settlementData
        );
    }

    function confirmSettlement(bytes32 obligationId, uint256 replenishAmount)
        external
        nonReentrant
        onlyRole(SETTLEMENT_MANAGER_ROLE)
    {
        AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
        AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId];
        if (obligation.status != AtomicTypes.ObligationStatus.SettlementPending) revert InvalidStatus();
        if (replenishAmount < obligation.destinationReserve) revert MinimumReplenishNotMet();

        bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
        liquidityVault.reconcileSettlement(corridorId, intent.assetOut, replenishAmount, msg.sender);
        fulfillerRegistry.releaseBond(obligationId);
        obligation.status = AtomicTypes.ObligationStatus.Settled;
        emit SettlementConfirmed(obligationId, replenishAmount);
    }

    function refundExpiredIntent(bytes32 obligationId) external nonReentrant {
        AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
        AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId];
        if (obligation.status != AtomicTypes.ObligationStatus.IntentCreated) revert InvalidStatus();
        if (block.timestamp <= intent.deadline) revert DeadlineNotReached();

        bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
        (, address payer,,,) = obligationEscrow.escrows(obligationId);
        liquidityVault.releaseReservation(obligationId);
        uint256 refunded = obligationEscrow.refundRemaining(obligationId, payer);
        _corridors[corridorId].degraded = true;
        obligation.status = AtomicTypes.ObligationStatus.Refunded;
        emit IntentRefunded(obligationId, refunded);
        emit CorridorDegraded(corridorId, obligationId, keccak256("FULFILMENT_TIMEOUT"));
    }

    function handleSettlementTimeout(bytes32 obligationId) external nonReentrant {
        AtomicTypes.AtomicIntent memory intent = _intents[obligationId];
        AtomicTypes.AtomicCommitment memory commitment = _commitments[obligationId];
        AtomicTypes.AtomicObligation storage obligation = _obligations[obligationId];
        if (obligation.status != AtomicTypes.ObligationStatus.SettlementPending) revert InvalidStatus();
        if (block.timestamp <= commitment.expiry) revert SettlementTimeoutNotReached();

        bytes32 corridorId = getCorridorId(intent.sourceChain, intent.destinationChain, intent.assetIn, intent.assetOut);
        _corridors[corridorId].degraded = true;
        slashingManager.slash(obligationId, protocolTreasury, keccak256("SETTLEMENT_TIMEOUT"));
        obligation.status = AtomicTypes.ObligationStatus.Slashed;
        emit CorridorDegraded(corridorId, obligationId, keccak256("SETTLEMENT_TIMEOUT"));
    }

    function getCorridorConfig(bytes32 corridorId) external view returns (AtomicTypes.CorridorConfig memory) {
        return _corridors[corridorId];
    }

    function getIntent(bytes32 obligationId) external view returns (AtomicTypes.AtomicIntent memory) {
        return _intents[obligationId];
    }

    function getCommitment(bytes32 obligationId) external view returns (AtomicTypes.AtomicCommitment memory) {
        return _commitments[obligationId];
    }

    function getObligation(bytes32 obligationId) external view returns (AtomicTypes.AtomicObligation memory) {
        return _obligations[obligationId];
    }
}
