import re
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from decimal import Decimal
from operator import itemgetter
from typing import Dict, List, Optional

import jinja2
from babel import dates as babel_dates

__all__ = ["env", "email_context_processor", "push_env"]


TEMPLATES_BY_FIELDS_TO_FORMAT = {
    "client": [
        "order_created_for_business",
        "order_changed_for_business",
        "order_cancelled_for_business",
    ],
    "order": [
        "order_created_for_business",
        "order_changed_for_business",
        "order_cancelled_for_business",
        "order_created",
        "order_changed",
        "order_cancelled",
        "order_reminder",
    ],
    "certificate": [
        "certificate_expired",
        "certificate_expiring",
        "certificate_purchased",
    ],
    "org": ["order_created", "order_changed", "order_cancelled", "order_reminder"],
}


def babel_datetime(
    value: datetime, format: str = "HH:mm, E, d MMM", locale: str = "ru_RU"
) -> str:
    return babel_dates.format_datetime(value, format=format, locale=locale)


def format_phone(phone: Optional[int]) -> str:
    if not phone:
        return "-"

    phone = str(phone)
    formatted_phone = "{} ({}) {}-{}-{}".format(
        phone[0], phone[1:4], phone[4:7], phone[7:9], phone[9:]
    )

    return (
        f"+{formatted_phone}"
        if not formatted_phone.startswith("8")
        else formatted_phone
    )


def earliest_order_booking(
    order: Dict[str, List[Dict[str, datetime]]], tz_offset: Optional[timedelta] = None
) -> Optional[datetime]:
    """Returns earliest order booking optionally with provided tz_offset."""
    if not order["items"]:
        return None

    earliest = sorted(map(itemgetter("booking_timestamp"), order["items"]))[0]
    if tz_offset is not None and not isinstance(tz_offset, jinja2.Undefined):
        earliest = earliest.astimezone(timezone(tz_offset))

    return earliest


def format_price(price: Decimal) -> str:
    result = []
    price = str(price)
    if "." in price:
        integer, frac = price.split(".")
    else:
        integer, frac = price, None

    for i, c in enumerate(reversed(integer)):
        result.append(c)
        if i % 3 == 2:
            result.append("&nbsp;")

    if frac is not None:
        if len(frac) == 1:
            frac += "0"
        result.insert(0, f".{frac}")

    result.insert(0, "&nbsp;₽")
    if result[-1] == "&nbsp;":
        result.pop()

    return "".join(reversed(result))


def format_cost(cost: dict) -> None:
    if cost.get("final_cost") is not None:
        cost["final_cost"] = format_price(cost["final_cost"])
    elif cost.get("minimal_cost") is not None:
        cost["minimal_cost"] = format_price(cost["minimal_cost"])

    if "cost_before_discounts" in cost:
        cost["cost_before_discounts"] = format_price(cost["cost_before_discounts"])
    if "discount" in cost:
        cost["discount"]["percent"] = str(cost["discount"]["percent"]) + "%"
        cost["discount"]["value"] = format_price(cost["discount"]["value"])


def format_order(order: dict, org: dict) -> None:
    # Add earliest_order_booking for subject
    order["earliest_order_booking"] = babel_datetime(
        value=earliest_order_booking(order=order, tz_offset=org.get("tz_offset")),
        format="HH:mm, E, d MMMM",
    )

    # Process order cost
    format_cost(order["total_cost"])

    for item in order["items"]:
        # Process order item cost
        format_cost(item["cost"])
        # Process order item booking_timestamp
        booking_timestamp = item["booking_timestamp"]
        if org.get("tz_offset") is not None:
            booking_timestamp = booking_timestamp.astimezone(timezone(org["tz_offset"]))
        booking_timestamp = babel_datetime(
            booking_timestamp, format="HH:mm, EEEE, d&n;MMMM YYYY&n;г."
        )
        item["booking_timestamp"] = booking_timestamp.replace("&n;", "&nbsp;")


def format_certificate(certificate: dict) -> None:
    if "sales" in certificate:
        certificate["sales"] = format_price(certificate["sales"])
    if "price" in certificate:
        certificate["price"] = format_price(certificate["price"])
    if "validity_period" in certificate:
        certificate["validity_period"] = {
            "valid_from": babel_datetime(
                certificate["validity_period"]["valid_from"],
                format="d MMMM YYYYг.",
            ),
            "valid_to": babel_datetime(
                certificate["validity_period"]["valid_to"], format="d MMMM YYYYг."
            ),
        }
    if "discount" in certificate:
        certificate["discount"] = str(certificate["discount"]) + "%"


def format_org(org: dict) -> None:
    if "url" in org:
        org["url_stripped"] = re.sub("^https?:\/\/", "", org["url"])  # noqa
    if "categories" in org:
        cats = []
        for cat in org["categories"]:
            cats.append(cat[0].lower() + cat[1:])
        cats[0] = cats[0][0].upper() + cats[0][1:]
        org["categories"] = ", ".join(cats)


def format_client(client: dict) -> None:
    if "phone" in client:
        client["phone"] = format_phone(client["phone"])


def email_context_processor(template: str, args: dict) -> dict:
    args = deepcopy(args)

    if template in TEMPLATES_BY_FIELDS_TO_FORMAT["order"]:
        format_order(order=args["order"], org=args["org"])

    if template in TEMPLATES_BY_FIELDS_TO_FORMAT["client"]:
        format_client(args["client"])

    if template in TEMPLATES_BY_FIELDS_TO_FORMAT["org"]:
        format_org(args["org"])

    if template in TEMPLATES_BY_FIELDS_TO_FORMAT["certificate"]:
        format_certificate(args["certificate"])

    # Remove timedelta as not JSON-serializable
    args["org"].pop("tz_offset", None)

    return args


env = jinja2.Environment(
    loader=jinja2.FileSystemLoader("lib/templates"), trim_blocks=True
)
env.filters.update(
    babel_datetime=babel_datetime, earliest_order_booking=earliest_order_booking
)

push_env = jinja2.Environment(
    loader=jinja2.FileSystemLoader("lib/templates/push"), trim_blocks=True
)
push_env.filters.update(
    babel_datetime=babel_datetime, earliest_order_booking=earliest_order_booking
)
