//! Order management and processing.

use std::fmt;
use uuid::Uuid;
use chrono::{NaiveDateTime, Utc};

use diesel_async::AsyncConnection;

use crate::aquire_pool;
use crate::error::ShopsterError;
use crate::baskets::Baskets;
use crate::postgresql::dborder::DbOrder;
use crate::postgresql::dborder::DbOrderItem;
use crate::postgresql::dborder::DbOrderStatus;
use crate::postgresql::dborder::DbPaymentStatus;
use crate::postgresql::dbwarehouse::DbWarehouse;

/// The lifecycle status of an order.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum OrderStatus {
    New,
    InProgress,
    ReadyToShip,
    Shipping,
    Done,
    Cancelled,
}

impl fmt::Display for OrderStatus {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl From<DbOrderStatus> for OrderStatus {
    fn from(status: DbOrderStatus) -> Self {
        match status {
            DbOrderStatus::New => OrderStatus::New,
            DbOrderStatus::InProgress => OrderStatus::InProgress,
            DbOrderStatus::ReadyToShip => OrderStatus::ReadyToShip,
            DbOrderStatus::Shipping => OrderStatus::Shipping,
            DbOrderStatus::Done => OrderStatus::Done,
            DbOrderStatus::Cancelled => OrderStatus::Cancelled,
        }
    }
}

impl From<OrderStatus> for DbOrderStatus {
    fn from(status: OrderStatus) -> Self {
        match status {
            OrderStatus::New => DbOrderStatus::New,
            OrderStatus::InProgress => DbOrderStatus::InProgress,
            OrderStatus::ReadyToShip => DbOrderStatus::ReadyToShip,
            OrderStatus::Shipping => DbOrderStatus::Shipping,
            OrderStatus::Done => DbOrderStatus::Done,
            OrderStatus::Cancelled => DbOrderStatus::Cancelled,
        }
    }
}

/// The payment status of an order, tracked independently of fulfillment.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PaymentStatus {
    Pending,
    Paid,
    Failed,
    Refunded,
}

impl fmt::Display for PaymentStatus {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl From<DbPaymentStatus> for PaymentStatus {
    fn from(status: DbPaymentStatus) -> Self {
        match status {
            DbPaymentStatus::Pending => PaymentStatus::Pending,
            DbPaymentStatus::Paid => PaymentStatus::Paid,
            DbPaymentStatus::Failed => PaymentStatus::Failed,
            DbPaymentStatus::Refunded => PaymentStatus::Refunded,
        }
    }
}

impl From<PaymentStatus> for DbPaymentStatus {
    fn from(status: PaymentStatus) -> Self {
        match status {
            PaymentStatus::Pending => DbPaymentStatus::Pending,
            PaymentStatus::Paid => DbPaymentStatus::Paid,
            PaymentStatus::Failed => DbPaymentStatus::Failed,
            PaymentStatus::Refunded => DbPaymentStatus::Refunded,
        }
    }
}

/// Price information captured in an order item.
#[derive(Debug, Clone, PartialEq)]
pub struct OrderItemPrice {
    pub amount: i64,
    pub currency: String,
}

/// A snapshot of a product as it was at order time.
#[derive(Debug, Clone, PartialEq)]
pub struct OrderItemSnapshot {
    pub id: i64,
    pub product_id: i64,
    pub quantity: i64,
    pub article_number: String,
    pub gtin: String,
    pub title: String,
    pub short_description: String,
    pub description: String,
    pub tags: Vec<String>,
    pub title_image: String,
    pub additional_images: Vec<String>,
    pub price: OrderItemPrice,
    pub weight: i64,
}

impl From<&DbOrderItem> for OrderItemSnapshot {
    fn from(db_item: &DbOrderItem) -> Self {
        let tags = db_item
            .tags
            .split('|')
            .filter(|tag| !tag.is_empty())
            .map(String::from)
            .collect();
        let additional_images = db_item
            .additional_images
            .split('|')
            .filter(|image| !image.is_empty())
            .map(String::from)
            .collect();

        OrderItemSnapshot {
            id: db_item.id,
            product_id: db_item.product_id,
            quantity: db_item.quantity,
            article_number: db_item.article_number.clone(),
            gtin: db_item.gtin.clone(),
            title: db_item.title.clone(),
            short_description: db_item.short_description.clone(),
            description: db_item.description.clone(),
            tags,
            title_image: db_item.title_image.clone(),
            additional_images,
            price: OrderItemPrice {
                amount: db_item.price,
                currency: db_item.currency.clone(),
            },
            weight: db_item.weight as i64,
        }
    }
}

impl From<&OrderItemSnapshot> for DbOrderItem {
    fn from(item: &OrderItemSnapshot) -> Self {
        DbOrderItem {
            id: item.id,
            order_id: 0,
            product_id: item.product_id,
            quantity: item.quantity,
            article_number: item.article_number.clone(),
            gtin: item.gtin.clone(),
            title: item.title.clone(),
            short_description: item.short_description.clone(),
            description: item.description.clone(),
            tags: item.tags.join("|"),
            title_image: item.title_image.clone(),
            additional_images: item.additional_images.join("|"),
            price: item.price.amount,
            currency: item.price.currency.clone(),
            weight: item.weight as i32,
            created_at: Utc::now().naive_utc(),
        }
    }
}

/// A complete order.
pub struct Order {
    pub id: i64,
    pub customer_id: Option<Uuid>,
    pub status: OrderStatus,
    pub delivery_address: String,
    pub billing_address: String,
    pub items: Vec<OrderItemSnapshot>,
    pub created_at: NaiveDateTime,
    pub updated_at: Option<NaiveDateTime>,
    pub payment_reference: Option<String>,
    pub payment_status: PaymentStatus,
}

impl From<&Order> for DbOrder {
    fn from(order: &Order) -> Self {
        DbOrder {
            id: order.id,
            customer_id: order.customer_id,
            status: order.status.into(),
            delivery_address: order.delivery_address.clone(),
            billing_address: order.billing_address.clone(),
            created_at: Utc::now().naive_utc(),
            updated_at: Some(Utc::now().naive_utc()),
            payment_reference: order.payment_reference.clone(),
            payment_status: order.payment_status.into(),
        }
    }
}

/// Handler for order management operations.
pub struct Orders {
    tenant_id: Uuid
}

impl Orders {
    pub fn new(tenant_id: Uuid) -> Self {
        Orders { tenant_id }
    }

    fn is_reserving_status(status: OrderStatus) -> bool {
        matches!(status, OrderStatus::New | OrderStatus::InProgress | OrderStatus::ReadyToShip)
    }

    fn is_valid_transition(from: OrderStatus, to: OrderStatus) -> bool {
        matches!(
            (from, to),
            (OrderStatus::New, OrderStatus::InProgress)
                | (OrderStatus::InProgress, OrderStatus::ReadyToShip)
                | (OrderStatus::ReadyToShip, OrderStatus::Shipping)
                | (OrderStatus::Shipping, OrderStatus::Done)
                | (OrderStatus::New, OrderStatus::Cancelled)
                | (OrderStatus::InProgress, OrderStatus::Cancelled)
                | (OrderStatus::ReadyToShip, OrderStatus::Cancelled)
                | (OrderStatus::Shipping, OrderStatus::Cancelled)
        )
    }

    pub async fn get_all(&self) -> Result<Vec<Order>, ShopsterError> {
        let db_orders = DbOrder::get_all(self.tenant_id).await?;
        let mut orders = Vec::new();

        for db_order in db_orders {
            let db_items = DbOrderItem::get_for_order(self.tenant_id, db_order.id).await?;
            let items = db_items.iter().map(OrderItemSnapshot::from).collect();

            orders.push(Order {
                id: db_order.id,
                customer_id: db_order.customer_id,
                status: db_order.status.into(),
                delivery_address: db_order.delivery_address,
                billing_address: db_order.billing_address,
                items,
                created_at: db_order.created_at,
                updated_at: db_order.updated_at,
                payment_reference: db_order.payment_reference,
                payment_status: db_order.payment_status.into(),
            });
        }

        Ok(orders)
    }

    pub async fn get_by_id(&self, order_id: i64) -> Result<Order, ShopsterError> {
        let db_order = DbOrder::find(self.tenant_id, order_id).await?;
        let db_items = DbOrderItem::get_for_order(self.tenant_id, db_order.id).await?;
        let items = db_items.iter().map(OrderItemSnapshot::from).collect();

        Ok(Order {
            id: db_order.id,
            customer_id: db_order.customer_id,
            status: db_order.status.into(),
            delivery_address: db_order.delivery_address,
            billing_address: db_order.billing_address,
            items,
            created_at: db_order.created_at,
            updated_at: db_order.updated_at,
            payment_reference: db_order.payment_reference,
            payment_status: db_order.payment_status.into(),
        })
    }

    pub async fn get_by_customer_id(&self, customer_id: Uuid) -> Result<Vec<Order>, ShopsterError> {
        let db_orders = DbOrder::get_by_customer_id(self.tenant_id, customer_id).await?;
        let mut orders = Vec::new();

        for db_order in db_orders {
            let db_items = DbOrderItem::get_for_order(self.tenant_id, db_order.id).await?;
            let items = db_items.iter().map(OrderItemSnapshot::from).collect();

            orders.push(Order {
                id: db_order.id,
                customer_id: db_order.customer_id,
                status: db_order.status.into(),
                delivery_address: db_order.delivery_address,
                billing_address: db_order.billing_address,
                items,
                created_at: db_order.created_at,
                updated_at: db_order.updated_at,
                payment_reference: db_order.payment_reference,
                payment_status: db_order.payment_status.into(),
            });
        }

        Ok(orders)
    }

    pub async fn get_without_customer_id(&self) -> Result<Vec<Order>, ShopsterError> {
        let db_orders = DbOrder::get_without_customer_id(self.tenant_id).await?;
        let mut orders = Vec::new();

        for db_order in db_orders {
            let db_items = DbOrderItem::get_for_order(self.tenant_id, db_order.id).await?;
            let items = db_items.iter().map(OrderItemSnapshot::from).collect();

            orders.push(Order {
                id: db_order.id,
                customer_id: db_order.customer_id,
                status: db_order.status.into(),
                delivery_address: db_order.delivery_address,
                billing_address: db_order.billing_address,
                items,
                created_at: db_order.created_at,
                updated_at: db_order.updated_at,
                payment_reference: db_order.payment_reference,
                payment_status: db_order.payment_status.into(),
            });
        }

        Ok(orders)
    }

    pub async fn get_by_payment_reference(&self, payment_reference: &str) -> Result<Option<Order>, ShopsterError> {
        let Some(db_order) = DbOrder::find_by_payment_reference(self.tenant_id, payment_reference).await? else {
            return Ok(None);
        };
        let db_items = DbOrderItem::get_for_order(self.tenant_id, db_order.id).await?;
        let items = db_items.iter().map(OrderItemSnapshot::from).collect();

        Ok(Some(Order {
            id: db_order.id,
            customer_id: db_order.customer_id,
            status: db_order.status.into(),
            delivery_address: db_order.delivery_address,
            billing_address: db_order.billing_address,
            items,
            created_at: db_order.created_at,
            updated_at: db_order.updated_at,
            payment_reference: db_order.payment_reference,
            payment_status: db_order.payment_status.into(),
        }))
    }

    pub async fn insert(&self, order: &Order) -> Result<Order, ShopsterError> {
        if order.delivery_address.trim().is_empty() {
            return Err(ShopsterError::InvalidOperationError(
                "Delivery address cannot be empty".to_string(),
            ));
        }
        if order.billing_address.trim().is_empty() {
            return Err(ShopsterError::InvalidOperationError(
                "Billing address cannot be empty".to_string(),
            ));
        }

        let pool = aquire_pool(self.tenant_id).await?;
        let mut conn = pool.get().await.map_err(|e| ShopsterError::DatabaseConnectionError(e.to_string()))?;

        let db_order = DbOrder::from(order);
        let mut db_items_input: Vec<DbOrderItem> = order.items.iter().map(DbOrderItem::from).collect();
        let is_reserving = Self::is_reserving_status(order.status);

        conn.transaction(async |conn| {
            let created_order = DbOrder::create_conn(conn, db_order).await?;

            for db_item in &mut db_items_input {
                db_item.order_id = created_order.id;
            }
            let created_items = DbOrderItem::create_for_order_conn(conn, db_items_input).await?;
            let items: Vec<OrderItemSnapshot> = created_items.iter().map(OrderItemSnapshot::from).collect();

            if is_reserving {
                for item in &items {
                    DbWarehouse::apply_reserved_delta_conn(conn, item.product_id, item.quantity).await?;
                }
            }

            Ok(Order {
                id: created_order.id,
                customer_id: created_order.customer_id,
                status: created_order.status.into(),
                delivery_address: created_order.delivery_address,
                billing_address: created_order.billing_address,
                items,
                created_at: created_order.created_at,
                updated_at: created_order.updated_at,
                payment_reference: created_order.payment_reference,
                payment_status: created_order.payment_status.into(),
            })
        }).await
    }

    pub async fn update(&self, order: &Order) -> Result<Order, ShopsterError> {
        let existing_order = DbOrder::find(self.tenant_id, order.id).await?;
        let existing_items = DbOrderItem::get_for_order(self.tenant_id, order.id).await?;
        let previous_status: OrderStatus = existing_order.status.into();
        let next_status: OrderStatus = order.status;

        if previous_status != next_status && !Self::is_valid_transition(previous_status, next_status) {
            return Err(ShopsterError::InvalidOperationError(format!(
                "Invalid order status transition: {} -> {}",
                previous_status, next_status
            )));
        }

        let pool = aquire_pool(self.tenant_id).await?;
        let mut conn = pool.get().await.map_err(|e| ShopsterError::DatabaseConnectionError(e.to_string()))?;

        let db_order = DbOrder::from(order);
        let order_id = order.id;
        let previous_reserving = Self::is_reserving_status(previous_status);
        let next_reserving = Self::is_reserving_status(next_status);
        let previous_snapshots: Vec<OrderItemSnapshot> = existing_items.iter().map(OrderItemSnapshot::from).collect();

        conn.transaction(async |conn| {
            let updated_order = DbOrder::update_conn(conn, order_id, db_order).await?;

            let db_items = DbOrderItem::get_for_order_conn(conn, updated_order.id).await?;
            let items: Vec<OrderItemSnapshot> = db_items.iter().map(OrderItemSnapshot::from).collect();

            if previous_reserving != next_reserving {
                let delta = if next_reserving { 1i64 } else { -1i64 };
                for item in &previous_snapshots {
                    DbWarehouse::apply_reserved_delta_conn(conn, item.product_id, item.quantity * delta).await?;
                }
            }

            Ok(Order {
                id: updated_order.id,
                customer_id: updated_order.customer_id,
                status: updated_order.status.into(),
                delivery_address: updated_order.delivery_address,
                billing_address: updated_order.billing_address,
                items,
                created_at: updated_order.created_at,
                updated_at: updated_order.updated_at,
                payment_reference: updated_order.payment_reference,
                payment_status: updated_order.payment_status.into(),
            })
        }).await
    }

    pub async fn remove(&self, order_id: i64) -> Result<bool, ShopsterError> {
        let existing_order = DbOrder::find(self.tenant_id, order_id).await?;
        let existing_items = DbOrderItem::get_for_order(self.tenant_id, order_id).await?;
        let existing_status: OrderStatus = existing_order.status.into();

        if Self::is_reserving_status(existing_status) {
            let pool = aquire_pool(self.tenant_id).await?;
            let mut conn = pool.get().await.map_err(|e| ShopsterError::DatabaseConnectionError(e.to_string()))?;
            for item in &existing_items {
                DbWarehouse::apply_reserved_delta_conn(&mut conn, item.product_id, -(item.quantity)).await?;
            }
        }

        let result = DbOrder::delete(self.tenant_id, order_id).await?;
        Ok(result > 0)
    }

    pub async fn create_from_basket(&self, basket_id: Uuid, delivery_address: String, billing_address: String, payment_reference: Option<String>) -> Result<Order, ShopsterError> {
        let baskets = Baskets::new(self.tenant_id);
        let basket_items = baskets.get_products_with_details(basket_id).await?;

        let mut items = Vec::new();
        for basket_item in basket_items {
            let product = basket_item.product;
            let price = product.price.ok_or_else(|| {
                ShopsterError::InvalidOperationError("Product price missing".to_string())
            })?;

            items.push(OrderItemSnapshot {
                id: 0,
                product_id: product.id,
                quantity: basket_item.quantity,
                article_number: product.article_number,
                gtin: product.gtin,
                title: product.title,
                short_description: product.short_description,
                description: product.description,
                tags: product.tags,
                title_image: product.image_url,
                additional_images: product.additional_images,
                price: OrderItemPrice {
                    amount: price.amount,
                    currency: price.currency,
                },
                weight: product.weight,
            });
        }

        let order = Order {
            id: 0,
            customer_id: None,
            status: OrderStatus::New,
            delivery_address,
            billing_address,
            items,
            created_at: Utc::now().naive_utc(),
            updated_at: None,
            payment_reference,
            payment_status: PaymentStatus::Pending,
        };

        self.insert(&order).await
    }

    /// Updates an order's payment status independently of its fulfillment status.
    ///
    /// This bypasses fulfillment transition validation, since payment state changes
    /// (e.g. a Stripe webhook confirming or refunding a payment) are orthogonal to
    /// the order's shipping lifecycle.
    pub async fn update_payment_status(&self, order_id: i64, payment_status: PaymentStatus) -> Result<Order, ShopsterError> {
        let updated_order = DbOrder::update_payment_status(self.tenant_id, order_id, payment_status.into()).await?;
        let db_items = DbOrderItem::get_for_order(self.tenant_id, updated_order.id).await?;
        let items = db_items.iter().map(OrderItemSnapshot::from).collect();

        Ok(Order {
            id: updated_order.id,
            customer_id: updated_order.customer_id,
            status: updated_order.status.into(),
            delivery_address: updated_order.delivery_address,
            billing_address: updated_order.billing_address,
            items,
            created_at: updated_order.created_at,
            updated_at: updated_order.updated_at,
            payment_reference: updated_order.payment_reference,
            payment_status: updated_order.payment_status.into(),
        })
    }
}
