"""Sync orders and products from e-računi (source of truth).

Order sync strategy — two SalesOrderList calls, results merged:
  1. dateFrom = 2 weeks ago  → all recent orders regardless of status
  2. status = Draft          → older drafts that may predate the window
  3. status = PartialDelivery→ older partially-delivered orders

No per-order API calls are made. No writes to e-računi during sync.
VAT/OIB fixes are applied when an individual order is processed (Step 1).
"""
import json
from datetime import datetime, timedelta
from typing import Generator

from app.services import eracuni as eracuni_svc
from app.db import upsert_orders, upsert_products, set_order_shopify_id

# Countries where the order language is Croatian / Serbo-Croatian
_HR_COUNTRIES = {'HR', 'BA', 'RS', 'ME', 'XK'}
# Countries where the order language is German
_DE_COUNTRIES = {'DE', 'AT', 'CH', 'LI'}


def _order_language(country: str) -> str:
    c = (country or '').strip().upper()
    if c in _HR_COUNTRIES:
        return 'HR'
    if c in _DE_COUNTRIES:
        return 'DE'
    return 'EN'


def _extract_delivery(raw: dict) -> dict:
    """Extract delivery/shipping address from a raw e-računi order dict.

    e-računi may provide it as flat fields (deliveryStreet, …) or as a nested
    Address / ShippingAddress object. Returns a dict with keys:
    name, street, postalCode, city, country  — all may be empty strings.
    """
    # Try flat fields first (most common in SalesOrderList)
    flat = {
        'name':       raw.get('deliveryName') or raw.get('shipToName') or '',
        'street':     raw.get('deliveryStreet') or raw.get('shipToStreet') or '',
        'postalCode': raw.get('deliveryPostalCode') or raw.get('shipToPostalCode') or '',
        'city':       raw.get('deliveryCity') or raw.get('shipToCity') or '',
        'country':    raw.get('deliveryCountry') or raw.get('shipToCountry') or '',
    }
    if any(flat.values()):
        return flat

    # Try nested Address / ShippingAddress object
    nested = raw.get('Address') or raw.get('address') or raw.get('ShippingAddress') or raw.get('shippingAddress') or {}
    if isinstance(nested, str):
        try:
            nested = json.loads(nested)
        except Exception:
            nested = {}
    if isinstance(nested, dict) and any(nested.values()):
        return {
            'name':       nested.get('name') or nested.get('buyerName') or '',
            'street':     nested.get('street') or nested.get('address1') or nested.get('address') or '',
            'postalCode': nested.get('postalCode') or nested.get('postal_code') or nested.get('zip') or '',
            'city':       nested.get('city') or '',
            'country':    nested.get('country') or nested.get('countryCode') or nested.get('country_code') or '',
        }
    return {'name': '', 'street': '', 'postalCode': '', 'city': '', 'country': ''}


def _normalize(raw: dict) -> dict:
    """Map a raw e-računi order dict to our DB schema."""
    items = raw.get('Items') or raw.get('items') or raw.get('orderItems') or []
    if isinstance(items, str):
        try:
            items = json.loads(items)
        except Exception:
            items = []

    delivery = _extract_delivery(raw)

    return {
        'number':               raw.get('number'),
        'date':                 raw.get('date'),
        'status':               raw.get('status'),
        'buyerName':            raw.get('buyerName'),
        'buyerName1':           raw.get('buyerName1'),
        'buyerStreet':          raw.get('buyerStreet'),
        'buyerPostalCode':      raw.get('buyerPostalCode'),
        'buyerCity':            raw.get('buyerCity'),
        'buyerCountry':         raw.get('buyerCountry'),
        'buyerEMail':           raw.get('buyerEMail'),
        'buyerPhone':           raw.get('buyerPhone'),
        'buyerTaxNumber':       raw.get('buyerTaxNumber'),
        'deliveryName':         delivery['name'],
        'deliveryStreet':       delivery['street'],
        'deliveryPostalCode':   delivery['postalCode'],
        'deliveryCity':         delivery['city'],
        'deliveryCountry':      delivery['country'],
        'items':                items,
        'deliveryMethod':       raw.get('deliveryMethod'),
        'methodOfPayment':      raw.get('methodOfPayment'),
        'totalAmountInDomCurr': raw.get('totalAmountInDomCurr'),
        'documentCurrency':     raw.get('documentCurrency'),
        'remarks':              raw.get('remarks'),
        'shopify_id':           raw.get('shopify_id'),
        'language':             _order_language(raw.get('buyerCountry', '')),
    }


def sync_orders(db_path: str) -> Generator[str, None, None]:
    """
    Generator: yields log strings while syncing orders from e-računi.

    Makes 3 SalesOrderList calls and merges results (deduplicated by number).
    """
    date_from = (datetime.now() - timedelta(weeks=2)).strftime('%Y-%m-%d')
    combined: dict[str, dict] = {}  # number → raw order dict

    # ── Call 1: recent orders (last 2 weeks, any status) ──────────────────────
    yield f'Fetching orders since {date_from}…'
    try:
        recent = eracuni_svc.get_orders(date_from)
        for o in recent:
            num = o.get('number')
            if num:
                combined[num] = o
        yield f'  → {len(recent)} recent orders'
    except Exception as e:
        yield f'ERROR (recent orders): {e}'
        return

    # ── Call 2: all PartialDelivery orders ───────────────────────────────────
    yield 'Fetching PartialDelivery orders…'
    try:
        partial = eracuni_svc.get_orders_by_status('PartialDelivery')
        added = 0
        for o in partial:
            num = o.get('number')
            if num and num not in combined:
                combined[num] = o
                added += 1
        yield f'  → {len(partial)} partial-delivery orders ({added} new outside window)'
    except Exception as e:
        yield f'WARNING (partial-delivery orders): {e}'

    # ── Normalise and save ─────────────────────────────────────────────────────
    total = len(combined)
    yield f'Merging {total} unique orders…'

    normalized = [_normalize(o) for o in combined.values()]
    upsert_orders(normalized, db_path)
    yield f'✓ Saved {total} orders to local database'


def sync_shopify_ids(db_path: str) -> Generator[str, None, None]:
    """Fetch all recent Shopify orders and write their IDs to matching local orders.

    Matching is done by Shopify order name (e.g. '#S1001') → e-računi number ('S1001').
    Only updates rows that exist in the local DB; does not create new rows.
    """
    from app.services import shopify as shopify_svc

    yield 'Fetching Shopify orders (last 60 days)…'
    try:
        shopify_orders = shopify_svc.get_all_orders(limit_days=60)
    except Exception as e:
        yield f'ERROR fetching Shopify orders: {e}'
        return

    yield f'Got {len(shopify_orders)} orders from Shopify'

    matched = 0
    for order in shopify_orders:
        shopify_id = str(order['id'])
        name = order.get('name', '')   # e.g. '#S1001'
        er_number = name.lstrip('#')   # → 'S1001'

        # Extract customer name from Shopify order
        customer = order.get('customer') or {}
        first = customer.get('first_name') or ''
        last  = customer.get('last_name') or ''
        customer_name = f'{first} {last}'.strip()
        if not customer_name:
            shipping = order.get('shipping_address') or {}
            customer_name = shipping.get('name', '') or ''

        financial_status    = order.get('financial_status') or None
        fulfillment_status  = order.get('fulfillment_status') or 'unfulfilled'

        if er_number and set_order_shopify_id(
            er_number, shopify_id, db_path,
            shopify_order_name=name,
            shopify_customer_name=customer_name or None,
            shopify_financial_status=financial_status,
            shopify_fulfillment_status=fulfillment_status,
        ):
            matched += 1

    yield f'✓ Linked {matched} of {len(shopify_orders)} Shopify orders to local e-računi orders'


def sync_products(db_path: str) -> Generator[str, None, None]:
    """Generator: yields log strings while syncing products from e-računi."""
    yield 'Fetching products from e-računi…'
    try:
        raw = eracuni_svc.get_products()
    except Exception as e:
        yield f'ERROR fetching products: {e}'
        return

    yield f'Got {len(raw)} products'

    products = [
        {
            'productCode':              p.get('productCode'),
            'name':                     p.get('name'),
            'englishName':              p.get('englishName'),
            'germanName':               p.get('germanName'),
            'barcode':                  p.get('barCode'),
            'defaultWarehouseLocation': p.get('defaultWarehouseLocation'),
        }
        for p in raw
        if p.get('productCode')
    ]

    upsert_products(products, db_path)
    yield f'✓ Saved {len(products)} products'
