"""
Checkout business logic services.
"""
import logging
import secrets
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

import bcrypt
from sqlalchemy import select
from sqlalchemy.orm import Session

from src.apps.checkouts import crud as checkout_crud
from src.apps.checkouts.helpers.payer_auth import create_payer_token, create_payer_refresh_token
from src.apps.checkouts.models.checkout import Checkout
from src.apps.checkouts.models.checkout_link import CheckoutLink
from src.core.exceptions import APIException, ConflictError, NotFoundError, UnauthorizedError

logger = logging.getLogger(__name__)

# Constant-time dummy hash for user-enumeration prevention (SEC pattern from HPP)
_DUMMY_HASH = "$2b$12$LQv3c1yqBwEHmBZAFHMpBOeRrigUlpjeA5wQvN2VDW5AMNNuqW1uW"


def _generate_token() -> str:
    return secrets.token_urlsafe(32)


# ─── Literal generation ───────────────────────────────────────────────────────

def generate_checkout_literal(db: Session) -> str:
    return checkout_crud.generate_checkout_literal(db)


# ─── Create ───────────────────────────────────────────────────────────────────

def create_checkout(db: Session, data: dict, merchant_id: int, user_id: Optional[int] = None) -> Checkout:
    literal = generate_checkout_literal(db)
    data["checkout_literal"] = literal
    initial_status = data.pop("status", "DRAFT")
    data["status"] = initial_status.upper() if initial_status else "DRAFT"
    if "checkout_type" in data and data["checkout_type"]:
        data["checkout_type"] = data["checkout_type"].upper()

    checkout = checkout_crud.create_checkout(db, data, merchant_id)

    # If status is ACTIVE on creation, auto-generate link
    if checkout.status.upper() == "ACTIVE":
        token = _generate_token()
        checkout_crud.create_checkout_link(db, checkout.id, token)

    checkout_crud.create_checkout_activity(
        db, checkout.id, merchant_id,
        "checkout.created",
        f"Checkout '{checkout.title}' created.",
        actor_user_id=user_id,
    )
    return checkout


# ─── Activate ─────────────────────────────────────────────────────────────────

def activate_checkout(db: Session, checkout: Checkout, merchant_id: int, user_id: Optional[int] = None) -> Checkout:
    if checkout.status.upper() == "ACTIVE":
        raise ConflictError(message="Checkout is already active.")
    if checkout.status.upper() == "EXPIRED":
        raise APIException(message="Cannot activate an expired checkout.", status_code=400)

    # Generate link if no active link exists
    active_link = checkout_crud.get_active_link(db, checkout.id)
    if active_link is None:
        token = _generate_token()
        checkout_crud.create_checkout_link(db, checkout.id, token)

    checkout.status = "ACTIVE"
    db.flush()

    checkout_crud.create_checkout_activity(
        db, checkout.id, merchant_id,
        "checkout.activated",
        "Checkout activated.",
        actor_user_id=user_id,
    )

    from src.events.dispatcher import EventDispatcher
    from src.events.base import BaseEvent
    import asyncio
    try:
        loop = asyncio.get_event_loop()
        loop.create_task(EventDispatcher.dispatch(BaseEvent(
            event_type="checkout.activated",
            data={"checkout_id": checkout.id, "merchant_id": merchant_id},
        )))
    except Exception:
        pass
    return checkout


# ─── Deactivate ───────────────────────────────────────────────────────────────

def deactivate_checkout(db: Session, checkout: Checkout, merchant_id: int, user_id: Optional[int] = None) -> Checkout:
    if checkout.status.upper() != "ACTIVE":
        raise APIException(message="Only ACTIVE checkouts can be deactivated.", status_code=400)

    active_link = checkout_crud.get_active_link(db, checkout.id)
    if active_link:
        checkout_crud.revoke_checkout_link(db, active_link)

    checkout.status = "INACTIVE"
    db.flush()

    checkout_crud.create_checkout_activity(
        db, checkout.id, merchant_id,
        "checkout.deactivated",
        "Checkout deactivated.",
        actor_user_id=user_id,
    )
    return checkout


# ─── Duplicate ────────────────────────────────────────────────────────────────

def duplicate_checkout(db: Session, checkout: Checkout, merchant_id: int, user_id: Optional[int] = None) -> Checkout:
    new_literal = generate_checkout_literal(db)
    new_data = {
        "checkout_literal": new_literal,
        "title": f"{checkout.title} (Copy)",
        "description": checkout.description,
        "checkout_type": checkout.checkout_type,
        "status": "DRAFT",
        "amount": checkout.amount,
        "currency": checkout.currency,
        "min_amount": checkout.min_amount,
        "max_amount": checkout.max_amount,
        "suggested_amounts": checkout.suggested_amounts,
        "default_amount": checkout.default_amount,
        "tip_enabled": checkout.tip_enabled,
        "tip_type": checkout.tip_type,
        "tip_percentages": checkout.tip_percentages,
        "payment_frequency": checkout.payment_frequency,
        "authorization_type": checkout.authorization_type,
        "split_config": checkout.split_config,
        "recurring_config": checkout.recurring_config,
        "require_payer_verification": checkout.require_payer_verification,
        "payer_verification_type": checkout.payer_verification_type,
        "allow_link_regeneration": checkout.allow_link_regeneration,
        "redirect_url": checkout.redirect_url,
        "thank_you_message": checkout.thank_you_message,
    }
    new_checkout = checkout_crud.create_checkout(db, new_data, merchant_id)
    checkout_crud.create_checkout_activity(
        db, new_checkout.id, merchant_id,
        "checkout.duplicated",
        f"Duplicated from {checkout.checkout_literal}.",
        actor_user_id=user_id,
    )
    return new_checkout


# ─── Regenerate link ──────────────────────────────────────────────────────────

def regenerate_link(db: Session, checkout: Checkout, merchant_id: int, user_id: Optional[int] = None) -> CheckoutLink:
    if not checkout.allow_link_regeneration:
        raise APIException(message="Link regeneration is disabled for this checkout.", status_code=400)

    active_link = checkout_crud.get_active_link(db, checkout.id)
    old_token = active_link.token if active_link else None
    if active_link:
        checkout_crud.revoke_checkout_link(db, active_link)

    new_token = _generate_token()
    new_link = checkout_crud.create_checkout_link(db, checkout.id, new_token)

    checkout_crud.create_checkout_activity(
        db, checkout.id, merchant_id,
        "checkout.link_regenerated",
        "Checkout link regenerated.",
        actor_user_id=user_id,
        metadata={"old_token": old_token, "new_token": new_token},
    )
    return new_link


# ─── Share ────────────────────────────────────────────────────────────────────

def share_checkout(db: Session, checkout: Checkout, share_data: dict, merchant_id: int, user_id: Optional[int] = None) -> dict:
    from src.events.dispatcher import EventDispatcher
    from src.events.base import BaseEvent
    import asyncio

    checkout_crud.create_checkout_activity(
        db, checkout.id, merchant_id,
        "checkout.shared",
        f"Checkout shared via {', '.join(share_data.get('channels', []))}.",
        actor_user_id=user_id,
        metadata=share_data,
    )
    try:
        loop = asyncio.get_event_loop()
        loop.create_task(EventDispatcher.dispatch(BaseEvent(
            event_type="checkout.shared",
            data={
                "checkout_id": checkout.id,
                "merchant_id": merchant_id,
                "recipients": share_data,
            },
        )))
    except Exception:
        pass
    return {"message": "Share request queued successfully."}


# ─── Public: load checkout ────────────────────────────────────────────────────

def get_public_checkout(db: Session, token: str) -> Dict[str, Any]:
    """Validate token, increment click count, return public-facing checkout data."""
    link = checkout_crud.get_link_by_token(db, token)
    if link is None:
        raise NotFoundError(message="Checkout link not found.")
    if link.status.upper() != "ACTIVE":
        if link.status.upper() == "EXPIRED":
            raise APIException(message="This checkout link has expired.", status_code=410)
        raise APIException(message="This checkout link is no longer active.", status_code=410)

    checkout = link.checkout
    if checkout is None or checkout.deleted_at is not None:
        raise NotFoundError(message="Checkout not found.")
    if checkout.status.upper() == "DRAFT":
        raise NotFoundError(message="Checkout not found.")
    if checkout.status.upper() in ("INACTIVE", "EXPIRED"):
        raise APIException(message="This checkout is no longer accepting payments.", status_code=410)

    # Check expiry
    if checkout.expires_at and checkout.expires_at < datetime.now(timezone.utc):
        raise APIException(message="This checkout has expired.", status_code=410)

    # Atomic click count increment
    checkout_crud.increment_link_click_count(db, link.id)

    # Load merchant branding
    merchant_branding = {}
    try:
        from sqlalchemy import select as sa_select
        from src.apps.merchants.models.merchant import Merchant
        merchant = db.execute(
            sa_select(Merchant).where(Merchant.id == checkout.merchant_id)
        ).scalar_one_or_none()
        if merchant:
            merchant_branding = {
                "merchant_name": getattr(merchant, "business_legal_name", None) or getattr(merchant, "name", None),
                "logo_url": None,
                "primary_color": None,
            }
    except Exception:
        pass

    return {
        "token": token,
        "checkout": checkout,
        "merchant_branding": merchant_branding,
    }


# ─── Public: check email ──────────────────────────────────────────────────────

def check_payer_email(db: Session, token: str, email: str) -> bool:
    """Returns True if this email has a payer account for the checkout's merchant."""
    link = checkout_crud.get_link_by_token(db, token)
    if link is None or link.status != "ACTIVE":
        raise NotFoundError(message="Checkout link not found or inactive.")

    merchant_id = link.checkout.merchant_id

    from sqlalchemy import select as sa_select
    from src.apps.users.models.user import User
    user = db.execute(
        sa_select(User).where(
            User.email == email,
            User.merchant_id == merchant_id,
            User.user_type == "customer",
            User.deleted_at == None,
        )
    ).scalar_one_or_none()
    return user is not None


# ─── Public: register ─────────────────────────────────────────────────────────

def register_payer(db: Session, token: str, data: dict) -> Dict[str, Any]:
    """Create User + Customer, return payer JWT."""
    link = checkout_crud.get_link_by_token(db, token)
    if link is None or link.status != "ACTIVE":
        raise NotFoundError(message="Checkout link not found or inactive.")

    checkout = link.checkout
    merchant_id = checkout.merchant_id

    from sqlalchemy import select as sa_select
    from src.apps.users.models.user import User
    from src.apps.customers.models.customer import Customer

    email = data["email"]
    password = data["password"]

    # Check if user already exists
    existing_user = db.execute(
        sa_select(User).where(User.email == email, User.deleted_at == None)
    ).scalar_one_or_none()

    if existing_user:
        raise ConflictError(message="An account with this email already exists. Please log in.")

    import secrets as _secrets
    hashed = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
    # Auto-generate a unique username for the payer account
    base_username = email.split("@")[0].lower().replace(".", "_")[:20]
    unique_suffix = _secrets.token_hex(4)
    username = f"{base_username}_{unique_suffix}"
    user = User(
        email=email,
        username=username,
        hashed_password=hashed,
        first_name=data.get("first_name", ""),
        last_name=data.get("last_name", ""),
        is_active=True,
        user_type="customer",
        merchant_id=merchant_id,
    )
    db.add(user)
    db.flush()

    # Create customer record scoped to this merchant
    from src.core.functions import generate_id
    customer_name = f"{data.get('first_name', '')} {data.get('last_name', '')}".strip() or email
    customer_uin = generate_id("uin", customer_name)
    customer_cid = generate_id("customer", customer_name)
    customer_literal = generate_id("customer", customer_name)
    customer = Customer(
        merchant_id=merchant_id,
        uin=customer_uin,
        customer_id=customer_cid,
        account_literal=customer_literal,
        first_name=data.get("first_name", ""),
        last_name=data.get("last_name", ""),
        email=email,
        phone=data.get("phone"),
        business_legal_name=data.get("business_legal_name"),
    )
    db.add(customer)
    db.flush()

    user.customer_id = customer.id
    db.flush()

    # Create CustomerContact record linked to the customer
    from src.apps.customers.models.customer_contact import CustomerContact
    contact_id = generate_id("contact", email)
    contact = CustomerContact(
        contact_id=contact_id,
        customer_id=customer.id,
        first_name=data.get("first_name", ""),
        last_name=data.get("last_name", ""),
        email=email,
        phone=data.get("phone"),
        is_active=True,
        user_account_id=user.id,
    )
    db.add(contact)
    db.flush()

    # Create Address record if address fields were provided
    address_line = data.get("address")
    addr_city = data.get("city")
    addr_state = data.get("state")
    addr_zip = data.get("zip")
    addr_country = data.get("country", "US")
    if address_line:
        from src.apps.base.models.address import Address
        from src.core.utils.enums import AddressTypes
        address_record = Address(
            address_line_1=address_line,
            city=addr_city,
            state=addr_state,
            zipcode=addr_zip,
            country=addr_country,
            address_type=AddressTypes.BILLING,
            use_as_default=True,
            is_active=True,
            merchant_id=merchant_id,
            customer_id=customer.id,
        )
        db.add(address_record)
        db.flush()

    access_token = create_payer_token(user.id, merchant_id, customer.id)
    refresh_token = create_payer_refresh_token(user.id, merchant_id, customer.id)
    return {
        "access_token": access_token,
        "refresh_token": refresh_token,
        "token_type": "bearer",
        "expires_in": 86400,
        "refresh_expires_in": 604800,
        "customer_id": customer.id,
        "user_id": user.id,
        "first_name": data.get("first_name", ""),
        "last_name": data.get("last_name", ""),
        "email": email,
        "phone": data.get("phone"),
        "address": address_line,
        "city": addr_city,
        "state": addr_state,
        "zip": addr_zip,
        "country": addr_country,
    }


# ─── Public: login ────────────────────────────────────────────────────────────

def login_payer(db: Session, token: str, email: str, password: str) -> Dict[str, Any]:
    link = checkout_crud.get_link_by_token(db, token)
    if link is None or link.status != "ACTIVE":
        raise NotFoundError(message="Checkout link not found or inactive.")

    checkout = link.checkout
    merchant_id = checkout.merchant_id

    from sqlalchemy import select as sa_select
    from src.apps.users.models.user import User

    user = db.execute(
        sa_select(User).where(
            User.email == email,
            User.merchant_id == merchant_id,
            User.user_type == "customer",
            User.deleted_at == None,
        )
    ).scalar_one_or_none()

    if not user:
        bcrypt.checkpw(password.encode("utf-8"), _DUMMY_HASH.encode("utf-8"))
        raise UnauthorizedError(message="Invalid email or password.")

    if not bcrypt.checkpw(password.encode("utf-8"), user.hashed_password.encode("utf-8")):
        raise UnauthorizedError(message="Invalid email or password.")

    if not user.is_active:
        raise UnauthorizedError(message="Account is inactive.")

    customer_id = getattr(user, "customer_id", None)
    access_token = create_payer_token(user.id, merchant_id, customer_id)
    refresh_token = create_payer_refresh_token(user.id, merchant_id, customer_id)

    # Fetch the customer's billing/default address to pre-populate billing info
    addr_line = addr_city = addr_state = addr_zip = addr_country = None
    if customer_id:
        from src.apps.customers.models.customer import Customer
        from src.apps.base.models.address import Address
        from src.core.utils.enums import AddressTypes
        customer = db.execute(
            select(Customer).where(Customer.id == customer_id, Customer.deleted_at == None)
        ).scalar_one_or_none()
        if customer and customer.addresses:
            billing_addr = next(
                (a for a in customer.addresses if a.address_type == AddressTypes.BILLING and not a.deleted_at),
                None,
            ) or next((a for a in customer.addresses if not a.deleted_at), None)
            if billing_addr:
                addr_line = billing_addr.address_line_1
                addr_city = billing_addr.city
                addr_state = billing_addr.state
                addr_zip = billing_addr.zipcode
                addr_country = billing_addr.country

    return {
        "access_token": access_token,
        "refresh_token": refresh_token,
        "token_type": "bearer",
        "expires_in": 86400,
        "refresh_expires_in": 604800,
        "customer_id": customer_id,
        "user_id": user.id,
        "first_name": getattr(user, "first_name", None),
        "last_name": getattr(user, "last_name", None),
        "email": user.email,
        "phone": getattr(user, "phone", None),
        "address": addr_line,
        "city": addr_city,
        "state": addr_state,
        "zip": addr_zip,
        "country": addr_country,
    }


# ─── Expire stale checkouts (used by Celery) ──────────────────────────────────

def expire_stale_checkouts(db: Session) -> int:
    """Mark all ACTIVE checkouts past their expires_at as EXPIRED. Returns count."""
    checkouts = checkout_crud.get_all_expiring_checkouts(db)
    count = 0
    from src.events.dispatcher import EventDispatcher
    from src.events.base import BaseEvent
    import asyncio

    for checkout in checkouts:
        checkout.status = "EXPIRED"
        for link in checkout.links:
            if link.status == "ACTIVE":
                link.status = "EXPIRED"
        checkout_crud.create_checkout_activity(
            db, checkout.id, checkout.merchant_id,
            "checkout.expired",
            "Checkout expired automatically.",
        )
        try:
            loop = asyncio.get_event_loop()
            loop.create_task(EventDispatcher.dispatch(BaseEvent(
                event_type="checkout.expired",
                data={"checkout_id": checkout.id, "merchant_id": checkout.merchant_id},
            )))
        except Exception:
            pass
        count += 1
    return count
