use std::ops::Neg;

use crate::addresses::Address;
use crate::line_item_options::LineItemOption;
use crate::line_items::LineItem;
use crate::{i18n, Cents, OrderData, ToView};
use chrono::Utc;
use num_traits::Zero;
use serde_json::Value as JsonValue;
use shopless_types::Address as AddressView;
use shopless_types::LineItem as LineItemView;
use shopless_types::LineItemOption as LineItemOptionView;
use shopless_types::Order as OrderView;

impl ToView<AddressView> for Address {
    fn to_view(&self, _: &i18n::Formatter) -> AddressView {
        AddressView {
            recipient: self.recipient.clone(),
            line1: self.line1.clone(),
            line2: self.line2.clone(),
            postal_code: self.postal_code.clone(),
            city: self.city.clone(),
            province_code: self.province_code.clone(),
            province_name: self.province_name.clone(),
            country_code: self.country_code,
            country_name: self.country_name.clone(),
            phone: self.phone.clone(),
            vat: self.vat.clone(),
        }
    }
}

impl ToView<LineItemView> for (&LineItem, &[LineItemOption]) {
    fn to_view(&self, i18n: &i18n::Formatter<'_>) -> LineItemView {
        let (line_item, options) = self;
        LineItemView {
            id: line_item.id,
            sku: line_item.sku.clone(),
            url: line_item.url.clone(),
            name: line_item.name.clone(),
            quantity: line_item.quantity,
            price: i18n.currency(line_item.price),
            price_cents: line_item.price.to_cents(),
            taxrate: line_item.taxrate,
            total: i18n.currency(line_item.total),
            total_cents: line_item.total.to_cents(),
            options: options
                .iter()
                .filter(|o| !matches!(o.value, JsonValue::Bool(false)))
                .map(|o| o.to_view(i18n))
                .collect(),
        }
    }
}

impl ToView<LineItemOptionView> for LineItemOption {
    fn to_view(&self, i18n: &i18n::Formatter<'_>) -> LineItemOptionView {
        let value = match self.value {
            JsonValue::Number(ref n) => {
                if let Some(n) = n.as_f64() {
                    // TODO: make the format configurable
                    match self.sku.as_str() {
                        "sph-left" | "sph-right" => i18n.plus_minus(n),
                        "cyl-left" | "cyl-right" => i18n.plus_minus(n),
                        "axs-left" | "axs-right" => i18n.f64(n) + "°",
                        _ => i18n.f64(n),
                    }
                } else {
                    n.to_string()
                }
            }
            _ => self.value.to_string(),
        };
        LineItemOptionView {
            sku: self.sku.clone(),
            name: self.name.clone(),
            value,
            price: i18n.currency(self.price),
            price_cents: self.price.to_cents(),
        }
    }
}

impl OrderData {
    pub fn to_view(&self) -> OrderView {
        self.to_localized_view(&self.order.locale)
    }

    pub fn to_localized_view(&self, locale: &str) -> OrderView {
        let i18n = i18n::Formatter::new(locale, &self.order.currency);
        ToView::to_view(self, &i18n)
    }
}

impl ToView<OrderView> for OrderData {
    fn to_view(&self, i18n: &i18n::Formatter<'_>) -> OrderView {
        let order = &self.order;
        let subtotal = (order.total - order.shipping).round();
        OrderView {
            id: order.id,
            origin: order.origin.clone(),
            locale: order.locale.clone(),
            custom_status: order.custom_status.clone(),
            date: i18n.date(&order.date),
            date_time: order.date,
            order_id: format_order_id(order.id),
            payment_method: order.payment_method.clone(),
            payment_method_name: self
                .invoice
                .as_ref()
                .map(|i| i.payment_method_name.to_string())
                .unwrap_or_else(|| order.payment_method.clone()),
            payment_instructions: self
                .invoice
                .as_ref()
                .and_then(|i| i.payment_instructions.clone()),
            invoice_number: self.invoice.as_ref().map(|i| i.invoice_id.to_string()),
            invoice_date: self.invoice.as_ref().map(|i| i.invoice_date),
            currency: order.currency.clone(),
            shipping_method: order.shipping_method.clone(),
            shipping_method_name: order.shipping_method_name.clone(),
            subtotal_cents: subtotal.to_cents(),
            subtotal: i18n.currency(subtotal),
            shipping: i18n.currency(order.shipping),
            shipping_cents: order.shipping.to_cents(),
            discount: if order.discount > Cents::zero() {
                Some(i18n.currency(order.discount.neg()))
            } else {
                None
            },
            tax: if order.tax > Cents::zero() {
                Some(i18n.currency(order.tax))
            } else {
                None
            },
            tax_cents: if order.tax > Cents::zero() {
                Some(order.tax.to_cents())
            } else {
                None
            },
            total: i18n.currency(order.total),
            total_cents: order.total.to_cents(),
            email: order.email.to_string(),
            invoice_address: self.invoice_address.to_view(&i18n),
            shipping_address: self.shipping_address.to_view(&i18n),
            line_items: self
                .line_items
                .iter()
                .map(|&(ref i, ref o)| (i, &o[..]).to_view(&i18n))
                .collect(),
            today: i18n.date(&Utc::now()),
            shipment: self.invoice.as_ref().and_then(|i| i.shipment.clone()),
            token: order.token.clone(),
            coupon: order.coupon.clone(),
        }
    }
}

pub fn format_order_id(id: i64) -> String {
    format!("{:08}", id)
}
