import requests
import json
from typing import Dict, Any, List, Optional
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import tool
from ..logger import log

_customer_context_cache: Dict[str, Dict[str, Any]] = {}


# ================= Helper Function =================
def shopify_post(
    query: str,
    variables: Optional[Dict] = None,
    config: Optional[RunnableConfig] = None,
) -> Dict[str, Any]:
    """
    Generic Shopify Admin GraphQL POST request using RunnableConfig.

    Args:
        query (str): GraphQL query string.
        variables (Optional[Dict]): Variables for the query.
        config (RunnableConfig): Must contain 'shop_domain' and 'access_token'.

    Returns:
        Dict[str, Any]: Parsed JSON response from Shopify.
    """
    if config is None:
        raise ValueError(
            "RunnableConfig is required with shop_domain and access_token."
        )

    shop_domain = config["configurable"].get("shop_domain")
    access_token = config["configurable"].get("access_token")

    if not shop_domain:
        raise ValueError("RunnableConfig must include 'shop_domain'.")
    if not access_token:
        raise ValueError("RunnableConfig must include 'access_token'.")

    url = f"https://{shop_domain}/admin/api/2025-07/graphql.json"

    payload = {"query": query}
    if variables:
        payload["variables"] = variables

    response = requests.post(
        url,
        headers={
            "Content-Type": "application/json",
            "X-Shopify-Access-Token": access_token,
        },
        data=json.dumps(payload),
    )

    try:
        data = response.json()
        log.info(f"data recieved::{data}")
    except Exception as e:
        raise Exception("Failed to parse Shopify response as JSON.", e)

    # if "errors" in data:
    #     raise Exception(f"Shopify API returned errors: {data['errors']}")

    return data


def normalize_orders_minimal(response: dict) -> str:
    """
    Parses Shopify Orders API response and returns a
    clean, human-readable Markdown summary.
    """

    if "errors" in response:
        raise ValueError(f"Shopify API returned errors: {response['errors']}")

    markdown_orders = []
    edges = response.get("data", {}).get("orders", {}).get("edges", []) or []

    for edge in edges:
        node = edge.get("node", {}) or {}

        # ---- Line Items ----
        line_items_md = ""
        for li in node.get("lineItems", {}).get("edges", []) or []:
            n = li.get("node", {}) or {}
            line_items_md += f"- 📦 **{n.get('title','')}** (x{int(n.get('quantity',0))})\n"

        if not line_items_md:
            line_items_md = "- *(No items found)*\n"

        # ---- Tracking URLs ----
        tracking_urls = []
        for f in node.get("fulfillments", []) or []:
            for ti in f.get("trackingInfo", []) or []:
                url = ti.get("url")
                if url:
                    tracking_urls.append(url)
        tracking_urls = list(set(tracking_urls))  # deduplicate

        tracking_md = ""
        if tracking_urls:
            for url in tracking_urls:
                tracking_md += f"- 🚚 [Track Shipment]({url})\n"
        else:
            tracking_md = "- *(No tracking info available)*\n"

        # ---- Assemble Markdown for this Order ----
        order_md = f"""
---

# 🛒 Order **{node.get('name','')}**
**Order ID:** `{node.get('id','')}`

## 📦 Line Items
{line_items_md}
## 🚚 Tracking Information
{tracking_md}
"""

        markdown_orders.append(order_md.strip())

    final_md = "\n\n".join(markdown_orders) if markdown_orders else "_No orders found._"

    return final_md

# ================= 1. Get Variants =================
@tool()
def get_variants(
    product_id: str,
    first: int = 10,
    config: RunnableConfig = None,
) -> Dict[str, Any]:
    """
    Fetch variants of a Shopify product by product ID.

    Args:
        product_id (str): Numeric or GID format product ID.
        first (int): Number of variants to fetch.

    Returns:
        Dict[str, Any]: Product title and variants.
    """
    if not product_id:
        raise ValueError("Product ID is required.")

    if not product_id.startswith("gid://shopify/Product/"):
        product_id = f"gid://shopify/Product/{product_id}"

    query = """
    query GetVariants($id: ID!, $first: Int!) {
    product(id: $id) {
        title
        variants(first: $first) {
        nodes {
            id
            title
            availableForSale
            inventoryQuantity
        }
        }
    }
    }
    """
    variables = {
        "id": product_id,
        "first": first,
    }

    return shopify_post(query, variables, config=config)


# ================= 2. Get Customer =================
@tool
def get_customer(
    customer_id: str,
    config: RunnableConfig = None,
) -> Dict[str, Any]:
    """
    Fetch Shopify customer details by ID.

    Args:
        customer_id (str): Numeric or GID format customer ID.

    Returns:
        Dict[str, Any]: Customer details including addresses.
    """
    if not customer_id:
        raise ValueError("Customer ID is required.")

    if not customer_id.startswith("gid://shopify/Customer/"):
        customer_id = f"gid://shopify/Customer/{customer_id}"

    query = """
    query GetCustomer($id: ID!) {
      customer(id: $id) {
        id
        email
        firstName
        lastName
        phone
        addresses {
          address1
          address2
          city
          province
          country
          zip
        }
      }
    }
    """
    variables = {"id": customer_id}
    return shopify_post(query, variables, config=config)


# ================= 3. Create Customer =================
@tool
def create_customer(
    email: str,
    first_name: str,
    last_name: str,
    phone: Optional[str] = None,
    addresses: Optional[List[Dict[str, str]]] = None,
    sms_marketing_consent: Optional[Dict[str, str]] = None,
    config: RunnableConfig = None,
) -> Dict[str, Any]:
    """
    Create a new customer in Shopify Admin.

    Args:
        email (str): Customer email (required).
        first_name (str): Customer first name (required).
        last_name (str): Customer last name (required).
        phone (str, optional): Customer phone number.
        addresses (List[Dict[str, str]], optional): Customer addresses.
        sms_marketing_consent (Dict[str, str], optional): Marketing consent info.
    Returns:
        Dict[str, Any]: Shopify customer creation response.
    """
    if not email:
        raise ValueError("Email is required.")
    if not first_name:
        raise ValueError("First name is required.")
    if not last_name:
        raise ValueError("Last name is required.")

    input_data = {"email": email, "firstName": first_name, "lastName": last_name}
    if phone:
        input_data["phone"] = phone
    if addresses:
        input_data["addresses"] = addresses
    if sms_marketing_consent:
        input_data["smsMarketingConsent"] = sms_marketing_consent

    query = """
    mutation CreateCustomer($input: CustomerInput!) {
      customerCreate(input: $input) {
        customer {
          id
          email
          firstName
          lastName
          phone
          addresses {
            address1
            address2
            city
            province
            country
            zip
          }
        }
        userErrors {
          field
          message
        }
      }
    }
    """
    variables = {"input": input_data}
    return shopify_post(query, variables, config=config)


# ================= 4. Create Draft Order =================
@tool
def create_draft_order(
    customer_id: str,
    line_items: List[Dict[str, Any]],
    shipping_address: Optional[Dict[str, str]] = None,
    config: RunnableConfig = None,
) -> Dict[str, Any]:
    """
    Create a Shopify draft order for a customer.

    Args:
        customer_id (str): Numeric or GID Shopify customer ID.
        line_items (List[Dict[str, Any]]): List of variantId and quantity.
        shipping_address (Dict[str, str], optional): Shipping address info.

    Returns:
        Dict[str, Any]: Shopify draft order id, invoice_url for payment and status.
    """
    if not customer_id:
        raise ValueError("Customer ID is required.")
    if not line_items or not isinstance(line_items, list):
        raise ValueError(
            "line_items must be a non-empty list of variants with quantity. "
            "Example: [{'variantId': 'gid://shopify/ProductVariant/1234567890', 'quantity': 2}]"
        )

    if not customer_id.startswith("gid://shopify/Customer/"):
        customer_id = f"gid://shopify/Customer/{customer_id}"

    for item in line_items:
        if not item["variantId"].startswith("gid://shopify/ProductVariant/"):
            item["variantId"] = f"gid://shopify/ProductVariant/{item['variantId']}"
        # Convert quantity to int (Shopify requires integer)
        item["quantity"] = int(item["quantity"])

    input_data = {
        "purchasingEntity": {"customerId": customer_id},
        "lineItems": line_items,
    }
    if shipping_address:
        input_data["shippingAddress"] = shipping_address

    log.info(f"final input data for create_draft_order::{input_data}")

    # query = """
    # mutation CreateDraftOrder($input: DraftOrderInput!) {
    #   draftOrderCreate(input: $input) {
    #     draftOrder {
    #       id
    #       name
    #       status
    #       lineItems(first: 10) {
    #         edges {
    #           node {
    #             id
    #             title
    #             quantity
    #           }
    #         }
    #       }
    #       shippingAddress {
    #         address1
    #         address2
    #         city
    #         province
    #         country
    #         zip
    #       }
    #     }
    #     userErrors {
    #       field
    #       message
    #     }
    #   }
    # }
    # """
    query = """
            mutation CreateDraftOrder($input: DraftOrderInput!) {
            draftOrderCreate(input: $input) {
                draftOrder {
                id
                invoiceUrl
                status
                }
                userErrors {
                field
                message
                }
            }
            }
            """
    variables = {"input": input_data}
    return shopify_post(query, variables, config=config)


# ================= 5. Get Orders By Email =================
@tool(return_direct=True)
def get_orders_by_email(
    email: str = None,
    first: int = 5,
    config: RunnableConfig = None,
):
    """
    Fetch Shopify orders for a given customer email.

    Args:
        email (str, optional): Customer email address. If not provided, will use config.
        first (int): Number of orders to fetch.

    Returns:
        Dict[str, Any]: Orders with details including line items, fulfillments, and customer info.
    """
    # Prefer email from config if available
    if config and getattr(config, "configurable", None):
        email_from_config = config.configurable.get("user_email")
        if email_from_config:
            email = email_from_config

    if not email:
        return "We’ll need your email to access order history — could you please provide it?"

    query = """
    query GetOrdersByEmail($emailQuery: String!, $first: Int) {
        orders(first: $first, query: $emailQuery) {
            edges {
                node {
                    id
                    name
                    createdAt
                    totalPriceSet {
                        shopMoney {
                            amount
                            currencyCode
                        }
                    }
                    customer {
                        id
                        email
                        firstName
                        lastName
                    }
                    lineItems(first: 5) {
                        edges {
                            node {
                                title
                                quantity
                                sku
                            }
                        }
                    }
                    fulfillments(first: 5) {
                        trackingInfo {
                            number
                            url
                            company
                        }
                        status
                    }
                }
            }
        }
    }
    """

    variables = {
        "emailQuery": f"email:{email}",
        "first": first,
    }

    order_history = shopify_post(query, variables, config=config)

    return normalize_orders_minimal(order_history)


@tool
def get_active_coupons(config: RunnableConfig = None) -> List[Dict[str, str]]:
    """Return up to 25 active coupon (code) discounts from Shopify with summary."""
    if config is None:
        raise ValueError(
            "RunnableConfig is required with shop_domain and access_token."
        )

    shop_domain = config["configurable"].get("shop_domain")
    access_token = config["configurable"].get("access_token")
    url = f"https://{shop_domain}/admin/api/2025-01/graphql.json"
    headers = {
        "X-Shopify-Access-Token": access_token,
        "Content-Type": "application/json",
    }

    query = """
    query {
      discountNodes(first: 25, query: "method:code AND status:active") {
        edges {
          node {
            discount {
              __typename
              ... on DiscountCodeBasic {
                title
                status
                summary
                codes(first: 10) { nodes { code } }
              }
              ... on DiscountCodeBxgy {
                title
                status
                summary
                codes(first: 10) { nodes { code } }
              }
              ... on DiscountCodeFreeShipping {
                title
                status
                summary
                codes(first: 10) { nodes { code } }
              }
            }
          }
        }
      }
    }
    """

    resp = requests.post(url, headers=headers, json={"query": query}, timeout=10)
    resp.raise_for_status()
    data = resp.json()

    discounts = []
    for edge in data["data"]["discountNodes"]["edges"]:
        d = edge["node"]["discount"]
        codes = [c["code"] for c in d.get("codes", {}).get("nodes", [])]
        discounts.append({
            "title": d.get("title"),
            "status": d.get("status"),
            "summary": d.get("summary"),
            "codes": codes,
        })
    return discounts

# def extract_order_prod_level(order_response: dict) -> dict:
#     """
#     Extract a detailed, production-ready summary of a Shopify order.

#     Includes:
#       - Customer details
#       - Order metadata
#       - Full product-level info
#       - Fulfillment & tracking info
#     """
#     order = order_response.get("data", {}).get("order", {})

#     # Basic order info
#     order_info = {
#         "id": order.get("id"),
#         "name": order.get("name"),
#         "email": order.get("email"),
#         "createdAt": order.get("createdAt"),
#         "totalAmount": order.get("totalPriceSet", {})
#         .get("shopMoney", {})
#         .get("amount"),
#         "currency": order.get("currencyCode"),
#     }

#     # Full product-level info
#     products = []
#     for item_edge in order.get("lineItems", {}).get("edges", []):
#         node = item_edge.get("node", {})
#         variant = node.get("variant", {})
#         products.append(
#             {
#                 "title": node.get("title"),
#                 "quantity": node.get("quantity"),
#                 "sku": node.get("sku"),
#                 "variant": {
#                     "id": variant.get("id"),
#                     "title": variant.get("title"),
#                     "price": variant.get("price"),
#                 },
#             }
#         )

#     # Fulfillment info
#     fulfillments = []
#     for f in order.get("fulfillments", []):
#         tracking_info = f.get("trackingInfo", [])
#         tracking_list = [
#             {
#                 "number": t.get("number"),
#                 "carrier": t.get("company"),
#                 "url": t.get("url"),
#             }
#             for t in tracking_info
#         ]
#         fulfillments.append({"status": f.get("status"), "tracking": tracking_list})

#     # Customer details
#     customer = order.get("customer", {})
#     customer_info = {
#         "id": customer.get("id"),
#         "firstName": customer.get("firstName"),
#         "lastName": customer.get("lastName"),
#         "fullName": f"{customer.get('firstName', '')} {customer.get('lastName', '')}".strip(),
#         "email": customer.get("email"),
#     }

#     # Production-ready structured summary
#     summary = {
#         "order": order_info,
#         "products": products,
#         "fulfillments": fulfillments,
#         "customer": customer_info,
#     }

#     return summary


def extract_order_prod_level(order_response: dict) -> str:
    """
    Extract a detailed, production-ready summary of a Shopify order,
    and return as a Markdown formatted string with nested lists for readability.
    """
    order = order_response.get("data", {}).get("order", {})

    # Basic order info
    order_info_md = (
        f"## Order: **{order.get('name','')}** (ID: {order.get('id','')})\n\n"
        f"- **Email:** {order.get('email','')}\n"
        f"- **Created At:** {order.get('createdAt','')}\n"
        f"- **Total Amount:** {order.get('totalPriceSet', {}).get('shopMoney', {}).get('amount','')}\n"
        f"- **Currency:** {order.get('currencyCode','')}\n"
    )

    # Full product-level info
    products_md = "### Products:\n"
    products_edges = order.get("lineItems", {}).get("edges", [])
    if products_edges:
        for item_edge in products_edges:
            node = item_edge.get("node", {})
            variant = node.get("variant", {})
            products_md += (
                f"- **{node.get('title','')}**\n"
                f"  - Quantity: {node.get('quantity','')}\n"
                f"  - SKU: {node.get('sku','')}\n"
                f"  - Variant:\n"
                f"    - ID: {variant.get('id','')}\n"
                f"    - Title: {variant.get('title','')}\n"
                f"    - Price: {variant.get('price','')}\n"
            )
    else:
        products_md += "- None\n"

    # Fulfillment info
    fulfillments_md = "### Fulfillments:\n"
    fulfillments = order.get("fulfillments", [])
    if fulfillments:
        for f in fulfillments:
            fulfillments_md += f"- **Status:** {f.get('status','')}\n"
            tracking_info = f.get("trackingInfo", [])
            if tracking_info:
                fulfillments_md += "  - Tracking Info:\n"
                for t in tracking_info:
                    fulfillments_md += (
                        f"    - Company: {t.get('company','')}\n"
                        f"      Number: {t.get('number','')}\n"
                        f"      URL: {t.get('url','')}\n"
                    )
            else:
                fulfillments_md += "  - No tracking info\n"
    else:
        fulfillments_md += "- None\n"

    # Customer details
    customer = order.get("customer", {})
    customer_md = (
        "### Customer:\n"
        f"- ID: {customer.get('id','')}\n"
        f"- Name: {customer.get('firstName','')} {customer.get('lastName','')}\n"
        f"- Email: {customer.get('email','')}\n"
    )

    # Combine all sections
    markdown_summary = "\n".join([order_info_md, products_md, fulfillments_md, customer_md])

    return markdown_summary




@tool(return_direct=True)
def get_order_details_by_id(
    order_id: str,
    config: RunnableConfig = None,
) -> str:
    """
    Fetch a Shopify order by its order ID.

    Args:
        order_id (str): Shopify order ID (e.g., gid://shopify/Order/1234567890)
    Returns:
        dict: Order details including line items, fulfillments, and customer info.
    """

    if not order_id:
        raise ValueError("order_id is required.")

    query = """
    query getOrderById($id: ID!) {
        order(id: $id) {
            id
            name
            email
            createdAt
            totalPriceSet {
                shopMoney {
                    amount
                    currencyCode
                }
            }
            currencyCode
            lineItems(first: 10) {
                edges {
                    node {
                        title
                        quantity
                        sku
                        variant {
                            id
                            title
                            price
                        }
                    }
                }
            }
            fulfillments(first: 5) {
                trackingInfo {
                    number
                    url
                    company
                }
                status
            }
            customer {
                id
                firstName
                lastName
                email
            }
        }
    }
    """

    variables = {"id": order_id}

    # Make the Shopify API call
    order_data = shopify_post(query, variables, config=config)

    # Normalize the output if needed
    normalized_order = extract_order_prod_level(order_data)

    return normalized_order
# @tool
# def get_customer_id_by_email(
#     customer_email: str,
#     config: RunnableConfig = None,
# ) -> str:
#     """
#     Fetch Shopify customer ID by email.

#     Args:
#         customer_email (str): Email of the customer to search for.
#         config (RunnableConfig): Must include 'shop_domain' and 'access_token'.

#     Returns:
#         str: Numeric customer ID if found, otherwise 'no customer_id found'.
#     """
#     log.info("get_customer_id_by_email invoked")
#     if not customer_email:
#         raise ValueError("Customer email is required.")

#     query = """
#     query GetCustomerByEmail($email: String!) {
#       customers(first: 1, query: $email) {
#         edges {
#           node { id email firstName lastName }
#         }
#       }
#     }
#     """
#     variables = {"email": customer_email}
#     log.info(f"recieved detail in get_customer_id_by_email:::,query is: {query}, variables are: {variables}")

#     result = shopify_post(query, variables=variables, config=config)
#     log.info(f"recieved result in get_customer_id_by_email:::,result is: {result}")

#     edges = result.get("data", {}).get("customers", {}).get("edges", [])
#     if edges:
#         # Extract numeric ID from Shopify GraphQL GID
#         return edges[0]["node"]["id"]
#     return "no customer_id found"
