"""کوئری‌های مشتری — پورت‌شده از core/repositories/customer_repo.py دسکتاپ (SQLAlchemy روی Postgres/SQLite)."""
import re
import time as _time
from datetime import timedelta

from flask import current_app
from sqlalchemy import case, exists, func, or_, update

from app.extensions import db
from app.models.customer import Customer, Purchase
from app.models.call import Call
from app.services.jalali import iran_today, to_en_digits, to_fa_digits, utc_now
from config import SLEEPING_AFTER_DAYS

STATUS_CHOICES = [("new", "جدید"), ("active", "فعال"), ("inactive", "غیرفعال"), ("sleeping", "خواب")]
GENDER_CHOICES = [("male", "آقا"), ("female", "خانم")]
HOW_FOUND_CHOICES = ["مشتری قدیمی", "معرفی دوستان", "اینستاگرام", "تابلو مغازه", "سایر"]

SORT_OPTIONS = [
    ("joined_desc", "جدیدترین عضویت"),
    ("weight_desc", "بیشترین خرید (وزن)"),
    ("weight_asc", "کمترین خرید (وزن)"),
    ("name", "نام (الفبا)"),
]
# آدرس‌های قدیمی (قبل از نمایش وزن به‌جای مبلغ که همیشه صفر بود) هنوز کار کنن
_LEGACY_SORTS = {"amount_desc": "weight_desc", "amount_asc": "weight_asc"}


def normalize_phone(raw):
    """یکدست‌سازی شماره تلفن: ارقام انگلیسی، بدون فاصله و خط تیره، 0098/+98 -> 0،
    موبایل بدون صفر اول -> با صفر، و «0» یا خالی -> None."""
    if raw is None:
        return None
    text = re.sub(r"[\s\-().]", "", to_en_digits(raw))
    if text.startswith("+98"):
        text = "0" + text[3:]
    elif text.startswith("0098"):
        text = "0" + text[4:]
    if re.fullmatch(r"9\d{9}", text):
        text = "0" + text
    if not text or set(text) == {"0"}:
        return None
    return text


def search_condition(search: str):
    """شرط جستجوی مشتری روی نام، نام خانوادگی، نام کامل، موبایل، تلفن ثابت و کد مشتری —
    عدد فارسی هم پیدا می‌شه و شماره با فاصله/خط تیره هم با شماره‌ی ذخیره‌شده مقایسه می‌شه."""
    text = (search or "").strip()
    # هر دو شکل ارقام: شماره‌هایی که قبلاً با عدد فارسی ذخیره شدن هم با تایپ انگلیسی پیدا بشن و برعکس
    variants = {text, to_en_digits(text), to_fa_digits(to_en_digits(text))}
    compact = re.sub(r"[\s\-()]", "", to_en_digits(text))
    if compact.isdigit():
        variants.update({compact, to_fa_digits(compact)})
    full_name = Customer.first_name + " " + Customer.last_name
    conditions = []
    for v in variants:
        for column in (Customer.first_name, Customer.last_name, full_name,
                       Customer.mobile_phone, Customer.landline_phone, Customer.customer_code):
            conditions.append(column.icontains(v, autoescape=True))
    return or_(*conditions)


def _purchase_stats_subq():
    return (
        db.session.query(
            Purchase.customer_id.label("customer_id"),
            func.sum(Purchase.amount).label("total_amount"),
            func.sum(Purchase.weight).label("total_weight"),
            func.count(Purchase.id).label("purchase_count"),
            func.max(Purchase.purchase_date).label("last_purchase_date"),
        )
        .group_by(Purchase.customer_id)
        .subquery()
    )


def _call_stats_subq():
    return (
        db.session.query(
            Call.customer_id.label("customer_id"),
            func.max(Call.call_date).label("last_call_date"),
            func.min(Call.call_date).label("first_call_date"),
        )
        .group_by(Call.customer_id)
        .subquery()
    )


def _filtered(query, search="", city=None, status=None, rating=None):
    query = query.filter(Customer.is_deleted.is_(False))
    if search:
        query = query.filter(search_condition(search))
    if city:
        query = query.filter(Customer.city == city)
    if status:
        query = query.filter(Customer.status == status)
    if rating:
        query = query.filter(Customer.rating == rating)
    return query


def list_customers(search="", city=None, status=None, rating=None, sort="joined_desc", limit=None):
    stats = _purchase_stats_subq()
    calls = _call_stats_subq()
    total_weight_expr = func.coalesce(stats.c.total_weight, 0)

    query = (
        db.session.query(
            Customer,
            func.coalesce(stats.c.total_amount, 0).label("total_amount"),
            total_weight_expr.label("total_weight"),
            calls.c.last_call_date,
        )
        .outerjoin(stats, stats.c.customer_id == Customer.id)
        .outerjoin(calls, calls.c.customer_id == Customer.id)
    )
    query = _filtered(query, search, city, status, rating)

    sort = _LEGACY_SORTS.get(sort, sort)
    order_map = {
        "joined_desc": [Customer.joined_at.desc()],
        "weight_desc": [total_weight_expr.desc()],
        "weight_asc": [total_weight_expr.asc()],
        "name": [Customer.first_name.asc(), Customer.last_name.asc()],
    }
    # شناسه به‌عنوان مرتب‌سازی آخر: ترتیب ردیف‌های هم‌ارز (مثلاً عضویت هم‌زمان در ورود اکسل) ثابت بمونه
    query = query.order_by(*order_map.get(sort, order_map["joined_desc"]), Customer.id.desc())
    if limit:
        query = query.limit(limit)

    results = []
    for customer, total_amount, total_weight, last_call_date in query.all():
        customer.computed_total_amount = total_amount or 0
        customer.computed_total_weight = total_weight or 0
        customer.computed_last_call_date = last_call_date
        results.append(customer)
    return results


def count_customers(search="", city=None, status=None, rating=None) -> int:
    return _filtered(Customer.query, search, city, status, rating).count()


def get_customer_with_stats(customer_id: int):
    stats = _purchase_stats_subq()
    calls = _call_stats_subq()
    row = (
        db.session.query(
            Customer,
            func.coalesce(stats.c.total_amount, 0).label("total_amount"),
            func.coalesce(stats.c.total_weight, 0).label("total_weight"),
            calls.c.last_call_date,
            calls.c.first_call_date,
        )
        .outerjoin(stats, stats.c.customer_id == Customer.id)
        .outerjoin(calls, calls.c.customer_id == Customer.id)
        .filter(Customer.id == customer_id)
        .first()
    )
    if not row:
        return None
    customer, total_amount, total_weight, last_call_date, first_call_date = row
    customer.computed_total_amount = total_amount or 0
    customer.computed_total_weight = total_weight or 0
    customer.computed_last_call_date = last_call_date
    customer.computed_first_call_date = first_call_date
    return customer


def search_by_name(q: str, limit=30):
    """جستجوی سریع مشتری (ثبت تماس، ثبت وزن) — با نام، نام کامل، شماره یا کد مشتری."""
    return (
        Customer.query.filter(Customer.is_deleted.is_(False), search_condition(q))
        .order_by(Customer.first_name, Customer.last_name)
        .limit(limit)
        .all()
    )


def distinct_cities():
    rows = (
        db.session.query(Customer.city)
        .filter(Customer.is_deleted.is_(False), Customer.city.isnot(None), Customer.city != "")
        .distinct()
        .order_by(Customer.city)
        .all()
    )
    return [r[0] for r in rows]


def next_customer_code():
    count = Customer.query.count()
    return f"C{count + 1001}"


# ---------------------------------------------------------------------------
# وضعیت خودکار مشتری: «غیرفعال» دستی است و دست نمی‌خوره؛ بقیه از روی تماس‌ها و خریدها حساب می‌شن:
#   فعال = تماس یا خرید در SLEEPING_AFTER_DAYS روز اخیر، خواب = سابقه داره ولی نه اخیراً، جدید = هیچ سابقه‌ای نداره
# ---------------------------------------------------------------------------

def _auto_status_expr():
    call_cutoff = utc_now() - timedelta(days=SLEEPING_AFTER_DAYS)  # call_date به UTC ذخیره می‌شه
    purchase_cutoff = iran_today() - timedelta(days=SLEEPING_AFTER_DAYS)
    recent = or_(
        exists().where(Call.customer_id == Customer.id, Call.call_date >= call_cutoff),
        exists().where(Purchase.customer_id == Customer.id, Purchase.purchase_date >= purchase_cutoff),
    )
    any_history = or_(
        exists().where(Call.customer_id == Customer.id),
        exists().where(Purchase.customer_id == Customer.id),
    )
    return case((recent, "active"), (any_history, "sleeping"), else_="new")


def refresh_statuses(customer_ids=None) -> int:
    """وضعیت خودکار مشتری‌ها رو دوباره حساب می‌کنه (فقط ردیف‌هایی که واقعاً عوض می‌شن نوشته می‌شن)."""
    new_status = _auto_status_expr()
    stmt = (
        update(Customer)
        .where(or_(Customer.status.is_(None), Customer.status != "inactive"))
        .where(or_(Customer.status.is_(None), Customer.status != new_status))
        .values(status=new_status)
        .execution_options(synchronize_session=False)
    )
    if customer_ids is not None:
        stmt = stmt.where(Customer.id.in_(list(customer_ids)))
    changed = db.session.execute(stmt).rowcount
    db.session.commit()
    return changed


_STATUS_REFRESH_SECONDS = 30 * 60
_last_status_refresh = None


def refresh_statuses_if_due():
    """با گذشت زمان مشتری فعال «خواب» می‌شه بدون اینکه اتفاقی بیفته؛ برای همین هر ۳۰ دقیقه
    (در هر پروسه‌ی سرور) کل وضعیت‌ها یک‌بار به‌روز می‌شن."""
    global _last_status_refresh
    now = _time.monotonic()
    if _last_status_refresh is not None and now - _last_status_refresh < _STATUS_REFRESH_SECONDS:
        return
    _last_status_refresh = now
    try:
        refresh_statuses()
    except Exception:
        db.session.rollback()
        current_app.logger.exception("به‌روزرسانی خودکار وضعیت مشتری‌ها انجام نشد")
