use crate::app::bond;
use crate::app::bond::TakerContext;
use crate::app::context::AppContext;
use crate::db::{buyer_has_pending_order, update_user_trade_index};
use crate::util::{
    enqueue_order_msg, get_dev_fee, get_fiat_amount_requested, get_market_amount_and_fee,
    get_order, set_waiting_invoice_status, show_hold_invoice, update_order_event, validate_invoice,
};
use mostro_core::prelude::*;
use nostr_sdk::prelude::*;
use sqlx::{Pool, Sqlite};
use sqlx_crud::Crud;

async fn update_order_status(
    order: &mut Order,
    my_keys: &Keys,
    pool: &Pool<Sqlite>,
    request_id: Option<u64>,
) -> Result<(), MostroError> {
    // Get buyer pubkey
    let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?;
    // Set order status to waiting buyer invoice
    match set_waiting_invoice_status(order, buyer_pubkey, request_id).await {
        Ok(_) => {
            // Update order status
            match update_order_event(my_keys, Status::WaitingBuyerInvoice, order).await {
                Ok(order_updated) => {
                    let _ = order_updated.update(pool).await;
                    Ok(())
                }
                Err(_) => Err(MostroInternalErr(ServiceError::UpdateOrderStatusError)),
            }
        }
        Err(_) => Err(MostroInternalErr(ServiceError::UpdateOrderStatusError)),
    }
}

pub async fn take_sell_action(
    ctx: &AppContext,
    msg: Message,
    event: &UnwrappedMessage,
    my_keys: &Keys,
) -> Result<(), MostroError> {
    let pool = ctx.pool();
    // Get order
    let mut order = get_order(&msg, pool).await?;

    // Get request id
    let request_id = msg.get_inner_message_kind().request_id;
    // Check if the seller has a pending order
    if buyer_has_pending_order(pool, event.identity.to_string()).await? {
        return Err(MostroCantDo(CantDoReason::PendingOrderExists));
    }

    // Check if the order is a sell order and if its status is active
    if let Err(cause) = order.is_sell_order() {
        return Err(MostroCantDo(cause));
    };
    // Accept takes against orders in either `Pending` (no taker yet) or
    // `WaitingTakerBond` (Phase 1.5: a prior concurrent taker is
    // mid-bond). Both are pre-trade from the take-validation
    // perspective; the locked-bond gate inside the bond block below
    // catches the genuine post-trade case.
    if order.check_status(Status::Pending).is_err()
        && order.check_status(Status::WaitingTakerBond).is_err()
    {
        return Err(MostroCantDo(CantDoReason::InvalidOrderStatus));
    }

    // Validate that the order was sent from the correct maker
    order
        .not_sent_from_maker(event.sender)
        .map_err(MostroCantDo)?;

    // Anti-abuse bond (Phase 1, concurrent-bonds model). The take
    // handler doesn't release prior bonds at retake-time anymore —
    // multiple `Requested` taker bonds coexist on the order and the
    // first to reach `Locked` wins. Three guards before this take
    // proceeds:
    //   1. A `Locked` *taker* bond already on the order means the
    //      trade is committed; reject with `PendingOrderExists`.
    //   2. The sender's own pubkey already has a `Requested` bond on
    //      this order → idempotent retry: re-send the same
    //      `PayInvoice` message and return.
    //   3. Otherwise fall through and create a fresh bond row
    //      alongside any prior `Requested` rows.
    // The order's taker fields are not mutated under the bond path —
    // they're stashed on the bond row's `taker_*` columns until the
    // winning bond locks.
    //
    // Phase 5: with `apply_to = both` the maker's own bond is already
    // `Locked` on every published order — that is the normal state, not
    // a committed trade. The committed-trade gate must therefore only
    // count `Locked` *taker* bonds; otherwise a locked maker bond would
    // wrongly block every taker with `PendingOrderExists`.
    let bond_required = bond::taker_bond_required();
    if bond_required {
        let active = crate::app::bond::db::find_active_bonds_for_order(pool, order.id).await?;
        if bond::trade_committed_by_locked_taker_bond(&active) {
            return Err(MostroCantDo(CantDoReason::PendingOrderExists));
        }
        let sender_str = event.sender.to_string();
        if let Some(existing) = active.iter().find(|b| b.pubkey == sender_str) {
            if let Some(bolt11) = existing.payment_request.clone() {
                let order_kind = order.get_order_kind().map_err(MostroInternalErr)?;
                let bond_small = SmallOrder::new(
                    Some(order.id),
                    Some(order_kind),
                    Some(Status::Pending),
                    existing.amount_sats,
                    order.fiat_code.clone(),
                    order.min_amount,
                    order.max_amount,
                    existing.taker_fiat_amount.unwrap_or(order.fiat_amount),
                    order.payment_method.clone(),
                    order.premium,
                    None,
                    None,
                    None,
                    None,
                    None,
                );
                enqueue_order_msg(
                    request_id,
                    Some(order.id),
                    Action::PayBondInvoice,
                    Some(Payload::PaymentRequest(Some(bond_small), bolt11, None)),
                    event.sender,
                    existing.taker_trade_index,
                )
                .await;
            }
            return Ok(());
        }
    }

    // Get seller pubkey
    let seller_pubkey = order.get_seller_pubkey().map_err(MostroInternalErr)?;

    // Get amount request if user requested one for range order - fiat amount will be used below
    // IMPORTANT: This must come BEFORE dev_fee calculation for market price orders
    if let Some(am) = get_fiat_amount_requested(&order, &msg) {
        order.fiat_amount = am;
    } else {
        return Err(MostroCantDo(CantDoReason::OutOfRangeSatsAmount));
    }

    // Calculate dev_fee BEFORE validate_invoice
    // Invoice validation needs the correct dev_fee to verify buyer invoice amount
    if order.has_no_amount() {
        // Market price: calculate amount, fee, and dev_fee
        match get_market_amount_and_fee(order.fiat_amount, &order.fiat_code, order.premium).await {
            Ok(amount_fees) => {
                order.amount = amount_fees.0;
                order.fee = amount_fees.1;
                let total_mostro_fee = order.fee * 2;
                order.dev_fee = get_dev_fee(total_mostro_fee);
            }
            Err(_) => return Err(MostroInternalErr(ServiceError::WrongAmountError)),
        };
    } else {
        // Fixed price: only calculate dev_fee (amount/fee already set at creation)
        let total_mostro_fee = order.fee * 2;
        order.dev_fee = get_dev_fee(total_mostro_fee);
    }

    // Validate invoice and get payment request if present
    // NOW dev_fee is set correctly for proper validation
    let payment_request = validate_invoice(&msg, &order).await?;

    let trade_index = match msg.get_inner_message_kind().trade_index {
        Some(trade_index) => trade_index,
        None => {
            if event.identity == event.sender {
                0
            } else {
                return Err(MostroInternalErr(ServiceError::InvalidPayload));
            }
        }
    };

    // Update trade index only after all checks are done. We bump
    // per-take (regardless of who wins the bond race) so the user's
    // monotonic trade-index counter stays consistent across attempts.
    update_user_trade_index(pool, event.identity.to_string(), trade_index)
        .await
        .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;

    // Concurrent-bonds path: stash this take's context on the bond
    // row (including the buyer payout invoice if supplied), leave the
    // order untouched. The winning bond's `on_bond_invoice_accepted`
    // callback will copy the `taker_*` snapshot onto the order at
    // lock-time and drive the trade flow.
    if bond_required {
        let taker_ctx = TakerContext {
            identity: event.identity.to_string(),
            trade_index,
            buyer_invoice: payment_request.clone(),
            fiat_amount: order.fiat_amount,
            amount: order.amount,
            fee: order.fee,
            dev_fee: order.dev_fee,
        };
        bond::request_taker_bond(pool, &order, event.sender, request_id, taker_ctx).await?;
        return Ok(());
    }

    // Non-bond path: legacy take. Persist the taker fields on the
    // order before driving the trade hold invoice.
    order.buyer_pubkey = Some(event.sender.to_string());
    order.master_buyer_pubkey = Some(event.identity.to_string());
    order.trade_index_buyer = Some(trade_index);
    order.set_timestamp_now();

    // If payment request is not present, update order status to waiting buyer invoice
    if payment_request.is_none() {
        update_order_status(&mut order, my_keys, pool, request_id).await?;
    }
    // If payment request is present, show hold invoice
    else {
        show_hold_invoice(
            my_keys,
            payment_request,
            &event.sender,
            &seller_pubkey,
            order,
            request_id,
        )
        .await?;
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    use mostro_core::order::{Kind as OrderKind, Status};
    use nostr_sdk::{Keys, Timestamp};
    use sqlx::SqlitePool;

    async fn create_test_pool() -> SqlitePool {
        SqlitePool::connect(":memory:").await.unwrap()
    }

    fn create_test_keys() -> Keys {
        Keys::generate()
    }

    /// Helper to build a test AppContext with the given pool.
    fn build_test_context(pool: SqlitePool) -> AppContext {
        use crate::app::context::test_utils::{test_settings, TestContextBuilder};
        TestContextBuilder::new()
            .with_pool(std::sync::Arc::new(pool))
            .with_settings(test_settings())
            .build()
    }

    fn create_test_message(trade_index: Option<u32>) -> Message {
        // Create a basic message for TakeSell action
        // We'll use the new_order method since TakeSell isn't directly available
        Message::new_order(
            Some(uuid::Uuid::new_v4()),
            Some(1),
            trade_index.map(|i| i as i64),
            Action::TakeSell,
            None, // We don't need payload for structure tests
        )
    }

    fn create_test_unwrapped_message() -> UnwrappedMessage {
        let identity = create_test_keys();
        let trade = create_test_keys();

        UnwrappedMessage {
            message: create_test_message(None),
            signature: None,
            sender: trade.public_key(),
            identity: identity.public_key(),
            created_at: Timestamp::now(),
        }
    }

    #[tokio::test]
    async fn test_update_order_status_structure() {
        // Test the structure of update_order_status function
        // This would require mocking Order, Keys, and database operations
        // No-op: structural test ensures no panic
    }

    #[tokio::test]
    async fn test_take_sell_action_pending_order_exists() {
        let pool = create_test_pool().await;
        let ctx = build_test_context(pool.clone());
        let keys = create_test_keys();
        let event = create_test_unwrapped_message();
        let msg = create_test_message(Some(1));

        // This test would require:
        // 1. Setting up database tables
        // 2. Creating a pending order for the buyer
        // 3. Mocking buyer_has_pending_order to return true
        let result = take_sell_action(&ctx, msg, &event, &keys).await;
        // Should fail if buyer has pending order, but we can't test that without DB setup
        assert!(result.is_ok() || result.is_err());
    }

    #[tokio::test]
    async fn test_take_sell_action_order_validation() {
        let pool = create_test_pool().await;
        let ctx = build_test_context(pool.clone());
        let keys = create_test_keys();
        let event = create_test_unwrapped_message();
        let msg = create_test_message(Some(1));

        // This test would require:
        // 1. Mocking get_order to return an order
        // 2. Setting up the order to be either valid or invalid
        let result = take_sell_action(&ctx, msg, &event, &keys).await;
        assert!(result.is_ok() || result.is_err());
    }

    #[tokio::test]
    async fn test_take_sell_action_trade_index_logic() {
        let pool = create_test_pool().await;
        let ctx = build_test_context(pool.clone());
        let keys = create_test_keys();

        // Test case 1: identity == sender, no trade_index
        let mut event = create_test_unwrapped_message();
        event.identity = event.sender;
        let msg = create_test_message(None);

        let result = take_sell_action(&ctx, msg, &event, &keys).await;
        // Should use trade_index = 0 when identity == sender
        assert!(result.is_ok() || result.is_err());

        // Test case 2: identity != sender, no trade_index
        let event2 = create_test_unwrapped_message();
        // identity and sender are already distinct by default
        let msg2 = create_test_message(None);

        let result2 = take_sell_action(&ctx, msg2, &event2, &keys).await;
        // Should fail with InvalidPayload when identity != sender and no trade_index
        if let Err(MostroInternalErr(ServiceError::InvalidPayload)) = result2 {}

        // Test case 3: with trade_index
        let msg3 = create_test_message(Some(1));
        let result3 = take_sell_action(&ctx, msg3, &event2, &keys).await;
        assert!(result3.is_ok() || result3.is_err());
    }

    #[tokio::test]
    async fn test_take_sell_action_market_price_calculation() {
        let pool = create_test_pool().await;
        let ctx = build_test_context(pool.clone());
        let keys = create_test_keys();
        let event = create_test_unwrapped_message();
        let msg = create_test_message(Some(1));

        // This test would require:
        // 1. Mocking get_order to return an order with amount = 0 (market price)
        // 2. Mocking get_market_amount_and_fee
        let result = take_sell_action(&ctx, msg, &event, &keys).await;
        assert!(result.is_ok() || result.is_err());
    }

    #[tokio::test]
    async fn test_take_sell_action_payment_request_flows() {
        let pool = create_test_pool().await;
        let ctx = build_test_context(pool.clone());
        let keys = create_test_keys();
        let event = create_test_unwrapped_message();

        // Test with no payment request (should update order status)
        let msg1 = create_test_message(Some(1));
        let result1 = take_sell_action(&ctx, msg1, &event, &keys).await;
        assert!(result1.is_ok() || result1.is_err());

        // Test with payment request (should show hold invoice)
        let msg2 = create_test_message(Some(1));
        let result2 = take_sell_action(&ctx, msg2, &event, &keys).await;
        assert!(result2.is_ok() || result2.is_err());
    }

    mod order_validation_tests {
        use super::*;

        #[test]
        fn test_order_validation_logic() {
            // Test the logical flow of order validation

            // Test sell order validation
            let order_kind = OrderKind::Sell;
            assert!(matches!(order_kind, OrderKind::Sell));

            // Test order status validation
            let order_status = Status::Pending;
            assert!(matches!(order_status, Status::Pending));

            // Test non-maker validation logic
            let maker_pubkey = create_test_keys().public_key();
            let taker_pubkey = create_test_keys().public_key();
            assert_ne!(maker_pubkey, taker_pubkey);
        }

        #[test]
        fn test_fiat_amount_range_logic() {
            // Test range order amount validation logic
            let requested_amount = 100i64;
            let min_amount = 50i64;
            let max_amount = 200i64;

            // Valid range
            assert!(requested_amount >= min_amount && requested_amount <= max_amount);

            // Out of range cases
            let too_small = 25i64;
            let too_large = 300i64;
            assert!(too_small < min_amount);
            assert!(too_large > max_amount);
        }
    }

    mod market_price_tests {

        #[test]
        fn test_market_price_calculation_logic() {
            // Test the logical flow of market price calculation
            let fiat_amount = 100i64;
            let premium = 5;

            // Mock calculation: amount = (fiat_amount / btc_price) * (1 + premium/100)
            let mock_btc_price = 50000.0;
            let base_amount = (fiat_amount as f64 / mock_btc_price) * 1e8;
            let premium_multiplier = 1.0 + (premium as f64 / 100.0);
            let final_amount = (base_amount * premium_multiplier) as i64;

            assert!(final_amount > 0);
            assert!(final_amount > base_amount as i64); // Should be higher due to premium
        }

        #[test]
        fn test_fee_calculation_logic() {
            // Test fee calculation structure
            let amount = 1_000_000i64; // 0.01 BTC
            let fee_rate = 0.005; // 0.5%
            let expected_fee = (amount as f64 * fee_rate) as i64;

            assert_eq!(expected_fee, 5_000); // 5000 sats
            assert!(expected_fee < amount); // Fee should be less than amount
        }
    }
}
