from datetime import datetime, timedelta
from typing import Optional, List

from fastapi import UploadFile, HTTPException
from sqlalchemy import select, and_, func, asc, desc, String, or_
from sqlalchemy.ext.asyncio import AsyncSession
from starlette import status

from src.customer.models import Customer, GoogleToken
from src.customer.schemas import CustomerCreate, CustomerResponse, CustomerUpdate
from src.error import _ERROR
from src.security import hash_password
from src.utils.image_manager import handle_image_upload, ASSETS_URL
from src.security import verify_password, create_access_token


class CustomerService:
    @staticmethod
    def normalize_phone(phone: str) -> str:
        digits = ''.join(c for c in phone if c.isdigit())
        if digits.startswith("0"):
            digits = "38" + digits
        elif not digits.startswith("38"):
            digits = "38" + digits
        return digits

    @staticmethod
    async def create_customer(db: AsyncSession, data: CustomerCreate, logo: UploadFile = None) -> Customer:
        now = datetime.utcnow()
        normalized_phone = CustomerService.normalize_phone(data.phone)

        # Перевірка, чи телефон уже зайнятий
        result = await db.execute(select(Customer).where(Customer.phone == normalized_phone))
        existing_customer = result.scalar_one_or_none()
        if existing_customer:
            _ERROR("CustomerPhoneExists")

        hashed_password = hash_password(data.password)
        logo_url = await handle_image_upload(logo) if logo else f"{ASSETS_URL}placeholder.webp"

        customer = Customer(
            name=data.name,
            phone=normalized_phone,
            password=hashed_password,
            comment=data.comment,
            logo=logo_url,
            date_added=now,
            date_modify=now,
            ip=None,
            auth_type="phone",
            banned=False,
        )
        db.add(customer)
        await db.commit()
        await db.refresh(customer)
        return customer

    @staticmethod
    async def get_customer_by_id(db: AsyncSession, customer_id: int) -> dict:
        result = await db.execute(select(Customer).where(Customer.customer_id == customer_id))
        customer = result.scalar_one_or_none()

        if not customer:
            _ERROR("CustomerNotFound")

        customer_response = CustomerResponse.model_validate(customer)
        return {"data": customer_response, "status": True}

    @staticmethod
    async def get_customers_list(
            db: AsyncSession,
            needle: Optional[str] = None,
            status: Optional[bool] = None,
            date_range: Optional[list[str]] = None,
            sort_field: str = "customer_id",
            sort_order: str = "ASC",
            page: int = 1,
            limit: int = 10,
    ) -> dict:
        order_func = asc if sort_order.upper() == "ASC" else desc

        sort_mapping = {
            "customer_id": Customer.customer_id,
            "name": Customer.name,
            "date_added": Customer.date_added
        }
        sort_column = sort_mapping.get(sort_field, Customer.customer_id)

        base_query = select(Customer)
        count_query = select(func.count()).select_from(Customer)

        filters = []

        if needle:
            like_term = f"%{needle.lower()}%"
            filters.append(or_(
                func.cast(Customer.customer_id, String).ilike(like_term),
                Customer.name.ilike(like_term),
                Customer.phone.ilike(like_term),
                Customer.ip.ilike(like_term),
            ))

        if status is not None:
            filters.append(Customer.banned == status)

        if date_range and len(date_range) == 2:
            start = datetime.fromisoformat(date_range[0])
            end = datetime.fromisoformat(date_range[1])
            filters.append(Customer.date_added.between(start, end))

        if filters:
            base_query = base_query.where(and_(*filters))
            count_query = count_query.where(and_(*filters))

        base_query = base_query.order_by(order_func(sort_column))
        base_query = base_query.offset((page - 1) * limit).limit(limit)

        result = await db.execute(base_query)
        customers = result.scalars().all()

        total_result = await db.execute(count_query)
        total = total_result.scalar()

        # Дашборд
        total_quantity_query = select(func.count()).select_from(Customer)
        if filters:
            total_quantity_query = total_quantity_query.where(and_(*filters))
        total_quantity_result = await db.execute(total_quantity_query)
        total_quantity = total_quantity_result.scalar()

        # Нові за місяць
        now = datetime.utcnow()
        start_new = datetime(now.year, now.month, 1)
        next_month = datetime(now.year + (now.month == 12), (now.month % 12) + 1, 1)
        end_new = next_month - timedelta(seconds=1)

        new_quantity_query = select(func.count()).select_from(Customer)
        if filters:
            new_quantity_query = new_quantity_query.where(and_(*filters))
        new_quantity_query = new_quantity_query.where(Customer.date_added.between(start_new, end_new))
        new_quantity_result = await db.execute(new_quantity_query)
        new_quantity = new_quantity_result.scalar()

        return {
            "items": customers,
            "total": total,
            "dashboard": {
                "total_quantity": total_quantity or 0,
                "new_quantity": new_quantity or 0,
            }
        }

    @staticmethod
    async def update_customer_by_id(
            db: AsyncSession,
            customer_id: int,
            data: CustomerUpdate,
            logo: Optional[UploadFile] = None
    ) -> dict:
        result = await db.execute(select(Customer).where(Customer.customer_id == customer_id))
        customer = result.scalar_one_or_none()

        if not customer:
            _ERROR("CustomerNotFound")

        if data.phone is not None:
            normalized_phone = CustomerService.normalize_phone(data.phone)
            # Перевіряємо чи не використовується іншим клієнтом
            existing = await db.execute(
                select(Customer).where(
                    Customer.phone == normalized_phone,
                    Customer.customer_id != customer_id
                )
            )
            if existing.scalar_one_or_none():
                _ERROR("CustomerPhoneExists")
            customer.phone = normalized_phone

        if data.name is not None:
            customer.name = data.name
        if data.comment is not None:
            customer.comment = data.comment
        if data.banned is not None:
            customer.banned = data.banned
        if data.password is not None:
            customer.password = hash_password(data.password)
        if logo:
            customer.logo = await handle_image_upload(logo)

        customer.date_modify = datetime.utcnow()

        await db.commit()
        await db.refresh(customer)

        response_data = CustomerResponse(
            customer_id=customer.customer_id,
            name=customer.name,
            phone=customer.phone,
            comment=customer.comment,
            logo=customer.logo,
            banned=customer.banned,
            auth_type=customer.auth_type,
            date_added=customer.date_added,
            date_modify=customer.date_modify
        )

        return {"data": response_data, "status": True}

    @staticmethod
    async def delete_customer_by_id(db: AsyncSession, customer_id: int) -> dict:
        result = await db.execute(select(Customer).where(Customer.customer_id == customer_id))
        customer = result.scalar_one_or_none()

        if not customer:
            _ERROR("CustomerNotFound")

        # Тут можна додати перевірку на пов'язані дані (замовлення, відгуки тощо)
        # if await CustomerService._has_related_data(customer_id, db):
        #     _ERROR("CustomerNotEmpty")

        await db.delete(customer)
        await db.commit()

        return {"status": True, "message": "Customer deleted successfully"}

    @staticmethod
    async def get_or_add_customer_google(db: AsyncSession, data):
        email = data["token_data"]["email"]
        result = await db.execute(select(Customer).where(Customer.email == email))
        customer = result.scalars().first()

        if not customer:
            now = datetime.utcnow()
            customer = Customer(
                name=data["token_data"]['name'],
                phone="",
                password="",
                comment="",
                logo=data["token_data"]['picture'],
                date_added=now,
                date_modify=now,
                ip=None,
                auth_type="google",
                banned=False,
                email=email,
            )
            db.add(customer)
            await db.commit()
            await db.refresh(customer)

            customer_id = customer.customer_id
            new_token = GoogleToken(
                access_token=data["data"]["access_token"],
                expires_in=data["data"]["expires_in"],
                refresh_token=data["data"]["refresh_token"],
                scope=data["data"]["scope"],
                token_type=data["data"]["token_type"],
                id_token=data["data"]["id_token"],
                date_added=now,
                customer_id=customer_id,
            )

            db.add(new_token)
            await db.commit()
            await db.refresh(new_token)

        account_type = "customer"
        account_data = {
            "customer_id": customer.customer_id,
            "name": customer.name,
            "email": customer.email,
            "logo": customer.logo,
            "auth_type": customer.auth_type,
            "banned": customer.banned,
            "date_added": customer.date_added.isoformat(),
            "date_modify": customer.date_modify.isoformat(),
        }
        account_id = customer.customer_id

        def serialize_for_jwt(obj):
            if isinstance(obj, dict):
                return {k: serialize_for_jwt(v) for k, v in obj.items()}
            if isinstance(obj, list):
                return [serialize_for_jwt(v) for v in obj]
            if isinstance(obj, datetime):
                return obj.isoformat()
            return obj

        token_payload = {
            "type": account_type,
            "account_data": serialize_for_jwt(account_data),
            "user_id": account_id
        }

        access_token = create_access_token(token_payload)

        return {
            "access_token": access_token,
            "token_type": "bearer",
            "account_data": account_data,
            "account_type": account_type
        }