import calendar
import uuid
import secrets
import string
import re

from datetime import datetime
from pydantic import BaseModel
from bson import ObjectId
from MongoDBConnection import db, users_collection
from controller.mail_helper import MailHelper


orders_collection = db["orders"]


# --- 1. SCHEMAS (Validate Input) ---
class UserGoogleLoginPayload(BaseModel):
    email: str
    firstName: str
    lastName: str
    avatarUrl: str


class UserLoginPayload(BaseModel):
    email: str
    password: str


class UserForgotPasswordPayload(BaseModel):
    email: str


class UserUpdatePasswordPayload(BaseModel):
    email: str
    newPassword: str


# --- 2. MODEL LOGIC (Xử lý DB & Nghiệp vụ) ---
class UserModel:
    @staticmethod
    def get_user_by_email(email: str):
        return users_collection.find_one({"email": email, "role": "user"})

    @staticmethod
    def get_user_by_id(user_id: str):
        try:
            return users_collection.find_one({"_id": ObjectId(user_id), "role": "user"})
        except Exception:
            return None

    @staticmethod
    def _safe_float(value, default=0.0):
        try:
            return float(value or 0)
        except (TypeError, ValueError):
            return float(default)

    @staticmethod
    def _calculate_order_total(items):
        if not isinstance(items, list):
            return 0.0

        total = 0.0
        for item in items:
            if not isinstance(item, dict):
                continue

            quantity = UserModel._safe_float(item.get("quantity"))
            price = UserModel._safe_float(item.get("price"))
            shipping = UserModel._safe_float(item.get("shipping"))
            tax = UserModel._safe_float(item.get("tax"))

            total += quantity * price + shipping + tax

        return round(total, 2)

    @staticmethod
    def _resolve_order_total(order_doc):
        total = order_doc.get("total")
        try:
            return round(float(total), 2)
        except (TypeError, ValueError):
            return UserModel._calculate_order_total(order_doc.get("items", []))

    @staticmethod
    def _current_month_range():
        now = datetime.utcnow()
        year = now.year
        month = now.month
        start = f"{year:04d}-{month:02d}-01"
        end_day = calendar.monthrange(year, month)[1]
        end = f"{year:04d}-{month:02d}-{end_day:02d}"
        return start, end

    @staticmethod
    def _order_date_key(order_doc):
        created_at = str(order_doc.get("createdAt") or "").strip()
        if created_at:
            return created_at[:10]

        date_value = str(order_doc.get("date") or "").strip()
        if len(date_value) >= 10:
            return date_value[:10]

        return date_value

    @staticmethod
    def _is_in_period(order_doc, date_from=None, date_to=None):
        period_from = (date_from or "").strip()
        period_to = (date_to or "").strip()

        if not period_from and not period_to:
            period_from, period_to = UserModel._current_month_range()

        order_date = UserModel._order_date_key(order_doc)
        if not order_date:
            return False

        if period_from and order_date < period_from:
            return False

        if period_to and order_date > period_to:
            return False

        return True

    @staticmethod
    def _is_admin_cancelled(order_doc):
        return (
            str(order_doc.get("status") or "").strip() == "Cancelled"
            and str(order_doc.get("cancelledBy") or "").strip() == "admin"
        )

    @staticmethod
    def _is_paid_like_order(order_doc):
        status = str(order_doc.get("status") or "").strip()
        return status in ["Processing", "Printing", "Shipped"]

    @staticmethod
    def search_users(
        keyword: str = "",
        page: int = 1,
        page_size: int = 10,
        date_from: str = None,
        date_to: str = None,
        sort_by: str = "balance",
        sort_order: str = "desc",
    ):
        page = max(1, int(page))
        page_size = max(1, min(100, int(page_size)))

        query = {"role": "user"}

        keyword = (keyword or "").strip()
        if keyword:
            escaped_keyword = re.escape(keyword)
            regex = {"$regex": escaped_keyword, "$options": "i"}

            query["$or"] = [
                {"email": regex},
                {"firstName": regex},
                {"lastName": regex},
                {"phone": regex},
                {"address": regex},
                {"city": regex},
                {"state": regex},
                {"country": regex},
                {"zipCode": regex},
            ]

        users = list(users_collection.find(query))
        total = len(users)

        user_ids = [str(user.get("_id")) for user in users if user.get("_id")]
        stats_map = {
            user_id: {
                "totalSpent": 0.0,
                "periodOrderCount": 0,
                "periodSpent": 0.0,
            }
            for user_id in user_ids
        }

        if user_ids:
            order_cursor = orders_collection.find(
                {"id_khach_hang": {"$in": user_ids}},
                {
                    "id_khach_hang": 1,
                    "status": 1,
                    "cancelledBy": 1,
                    "total": 1,
                    "items": 1,
                    "date": 1,
                    "createdAt": 1,
                },
            )

            for order in order_cursor:
                customer_id = str(order.get("id_khach_hang") or "").strip()
                if not customer_id or customer_id not in stats_map:
                    continue

                # Bỏ các đơn admin đã hủy
                if UserModel._is_admin_cancelled(order):
                    continue

                order_total = UserModel._resolve_order_total(order)
                is_paid_like = UserModel._is_paid_like_order(order)
                is_in_period = UserModel._is_in_period(order, date_from, date_to)

                # Tổng đã chi toàn thời gian: chỉ tính đơn đã thanh toán / đã vào xử lý
                if is_paid_like:
                    stats_map[customer_id]["totalSpent"] += order_total

                # Số đơn trong khoảng lọc: tính tất cả đơn, chỉ bỏ admin cancelled
                if is_in_period:
                    stats_map[customer_id]["periodOrderCount"] += 1

                    # Tiền trong khoảng lọc: chỉ tính đơn đã thanh toán / đã vào xử lý
                    if is_paid_like:
                        stats_map[customer_id]["periodSpent"] += order_total

        items = []
        for user in users:
            user_id = str(user.get("_id"))
            stats = stats_map.get(
                user_id,
                {
                    "totalSpent": 0.0,
                    "periodOrderCount": 0,
                    "periodSpent": 0.0,
                },
            )

            first_name = (user.get("firstName", "") or "").strip()
            last_name = (user.get("lastName", "") or "").strip()
            full_name = f"{first_name} {last_name}".strip()

            total_balance = UserModel._safe_float(user.get("balance", 0))
            total_spent = round(UserModel._safe_float(stats.get("totalSpent", 0)), 2)
            period_order_count = int(stats.get("periodOrderCount", 0) or 0)
            period_spent = round(UserModel._safe_float(stats.get("periodSpent", 0)), 2)

            items.append(
                {
                    "id": user_id,
                    "name": full_name or user.get("email", ""),
                    "phone": user.get("phone", ""),
                    "email": user.get("email", ""),
                    "address": user.get("address", ""),
                    "city": user.get("city", ""),
                    "state": user.get("state", ""),
                    "country": user.get("country", ""),
                    "zipCode": user.get("zipCode", ""),
                    "totalBalance": round(total_balance, 2),

                    # field mới
                    "totalSpent": total_spent,
                    "periodOrderCount": period_order_count,
                    "periodSpent": period_spent,

                    # fallback field cũ cho FE cũ
                    "spent": total_spent,
                    "monthOrderCount": period_order_count,
                    "monthSpent": period_spent,
                }
            )

        reverse = str(sort_order or "desc").lower() != "asc"

        if str(sort_by or "spent").lower() == "spent":
            items.sort(
                key=lambda x: (
                    UserModel._safe_float(x.get("totalSpent", 0)),
                    UserModel._safe_float(x.get("totalBalance", 0)),
                    str(x.get("name", "")).lower(),
                ),
                reverse=reverse,
            )
        else:
            items.sort(
                key=lambda x: (
                    UserModel._safe_float(x.get("totalBalance", 0)),
                    UserModel._safe_float(x.get("totalSpent", 0)),
                    str(x.get("name", "")).lower(),
                ),
                reverse=reverse,
            )

        total_pages = (total + page_size - 1) // page_size if total > 0 else 1
        skip = (page - 1) * page_size
        paged_items = items[skip: skip + page_size]

        return {
            "items": paged_items,
            "total": total,
            "page": page,
            "pageSize": page_size,
            "totalPages": total_pages,
        }

    @staticmethod
    def add_balance(user_id: str, amount: float):
        result = users_collection.update_one(
            {"_id": ObjectId(user_id), "role": "user"},
            {"$inc": {"balance": float(amount)}}
        )
        return result.modified_count > 0

    @staticmethod
    def deduct_balance(user_id: str, amount: float):
        amount = float(amount)

        if amount <= 0:
            raise ValueError("Số tiền trừ không hợp lệ")

        user = UserModel.get_user_by_id(user_id)
        if not user:
            raise ValueError("Không tìm thấy user")

        current_balance = float(user.get("balance", 0) or 0)
        if current_balance < amount:
            raise ValueError(f"Số dư không đủ. Hiện có ${current_balance:.2f}, cần ${amount:.2f}")

        result = users_collection.update_one(
            {"_id": ObjectId(user_id), "role": "user", "balance": {"$gte": amount}},
            {"$inc": {"balance": -amount}}
        )

        if result.modified_count == 0:
            raise ValueError("Không thể trừ số dư, vui lòng thử lại")

        return round(current_balance - amount, 2)

    @staticmethod
    def handle_google_login(payload: UserGoogleLoginPayload):
        user = UserModel.get_user_by_email(payload.email)
        if not user:
            new_user = {
                "userId": str(uuid.uuid4()),
                "email": payload.email,
                "firstName": payload.firstName,
                "lastName": payload.lastName,
                "avatarUrl": payload.avatarUrl,
                "balance": 0,
                "auth_provider": "google",
                "password": "",
                "role": "user"
            }
            insert_result = users_collection.insert_one(new_user)
            new_user["_id"] = insert_result.inserted_id
            user = new_user
        return user

    @staticmethod
    def verify_login(email, password):
        user = UserModel.get_user_by_email(email)
        if user and user.get("password") == password:
            return user
        return None

    @staticmethod
    def reset_password(email):
        user = UserModel.get_user_by_email(email)
        if not user:
            return False, "Email không tồn tại trên hệ thống!"

        alphabet = string.ascii_letters + string.digits
        random_password = ''.join(secrets.choice(alphabet) for _ in range(8))

        users_collection.update_one({"email": email}, {"$set": {"password": random_password}})

        email_data = {
            "username": f"{user.get('firstName')} {user.get('lastName')}",
            "password": random_password,
            "note": "Vì lý do bảo mật, hãy đổi lại mật khẩu này ngay sau khi đăng nhập thành công."
        }

        mail_sent = MailHelper.send_new_password_email(email, email_data)
        if mail_sent:
            return True, "Mật khẩu mới đã được gửi vào Email của bạn!"
        return False, "Lỗi khi gửi email."

    @staticmethod
    def update_password(email, new_password):
        result = users_collection.update_one(
            {"email": email, "role": "user"},
            {"$set": {"password": new_password}}
        )
        return result.modified_count > 0