"""Premium configuration service (Premium Management v2).

The single source of truth for admin-configurable Premium settings — payment
details, plans and features. Everything user-facing reads through here, so no
payment info / price / feature copy is ever hardcoded. Reads are memoised per
request (``flask.g``) so a page needs only a handful of queries; the first access
seeds sensible defaults (mirroring the previous hardcoded values) so a fresh
install and the tests work with no manual setup.

Business rules for activation/approval are unchanged and stay in
:mod:`app.premium.services`; this module owns configuration only.
"""

from __future__ import annotations

import base64
import io
from urllib.parse import quote

from flask import g, has_request_context

from app.extensions import db
from app.models import PremiumFeature, PremiumPaymentSettings, PremiumPlan

# Defaults seeded on first use — identical to the values previously hardcoded in
# the templates, so behaviour is preserved until an admin edits them.
_DEFAULT_PAYMENT = {
    "active_upi_id": "studentbazaar@upi",
    "bank_name": "Student Bazaar Campus Bank",
    "account_holder": "Student Bazaar",
    "account_number": "0000 0000 0000",
    "ifsc": "SBZR0000001",
    "cash_instructions": "Hand your payment to a campus coordinator — no transaction reference needed.",
    "support_email": "premium@studentbazaar.app",
    "support_phone": "",
}
_DEFAULT_PLANS = [
    {"name": "Monthly", "duration_days": 30, "price": 99, "sort_order": 1,
     "description": "Billed monthly. Cancel anytime."},
    {"name": "Yearly", "duration_days": 365, "price": 999, "sort_order": 2,
     "description": "Best value — save over 15% versus monthly."},
]
_DEFAULT_FEATURES = [
    {"title": "Unlimited Messaging", "icon": "bi-chat-dots-fill", "display_order": 1,
     "description": "No 10-message cap — chat freely with any buyer or seller."},
    {"title": "Ad-Free Experience", "icon": "bi-badge-ad-fill", "display_order": 2,
     "description": "A clean, distraction-free marketplace with no advertisements."},
    {"title": "Premium Seller Badge", "icon": "bi-patch-check-fill", "display_order": 3,
     "description": "A verified premium badge that builds buyer trust."},
    {"title": "Featured Listings", "icon": "bi-stars", "display_order": 4,
     "description": "Feature a listing above standard results to boost discovery."},
    {"title": "Advanced Analytics", "icon": "bi-graph-up-arrow", "display_order": 5,
     "description": "Track views, saves and offer trends to sell smarter."},
    {"title": "Priority Support", "icon": "bi-headset", "display_order": 6,
     "description": "Front-of-the-queue help for account and Premium issues."},
]

CURRENCY_SYMBOL = "₹"
_PAYMENT_METHOD_LABELS = {"upi": "UPI", "bank_transfer": "Bank Transfer", "cash": "Cash"}


# --- Per-request memoisation ---------------------------------------------------

def _cache_get(key):
    if has_request_context():
        return getattr(g, "_premium_config_cache", {}).get(key)
    return None


def _cache_set(key, value):
    if has_request_context():
        cache = getattr(g, "_premium_config_cache", None)
        if cache is None:
            cache = {}
            g._premium_config_cache = cache
        cache[key] = value
    return value


def _invalidate():
    if has_request_context() and hasattr(g, "_premium_config_cache"):
        g._premium_config_cache = {}


# --- Payment settings ----------------------------------------------------------

def get_payment_settings() -> PremiumPaymentSettings:
    """The single payment-settings row, created with defaults on first access."""
    cached = _cache_get("settings")
    if cached is not None:
        return cached
    settings = db.session.scalar(db.select(PremiumPaymentSettings).limit(1))
    if settings is None:
        settings = PremiumPaymentSettings(**_DEFAULT_PAYMENT)
        db.session.add(settings)
        db.session.commit()
    return _cache_set("settings", settings)


def update_payment_settings(**fields) -> PremiumPaymentSettings:
    """Update the active payment settings (admin). Unknown keys are ignored."""
    settings = get_payment_settings()
    for key in ("active_upi_id", "bank_name", "account_holder", "account_number", "ifsc",
                "cash_instructions", "support_email", "support_phone"):
        if key in fields:
            value = (fields[key] or "").strip() or None
            setattr(settings, key, value)
    for flag in ("enable_upi", "enable_bank", "enable_cash"):
        if flag in fields:
            setattr(settings, flag, bool(fields[flag]))
    db.session.commit()
    _invalidate()
    return settings


def get_enabled_payment_methods() -> list[tuple[str, str]]:
    """``(value, label)`` for each currently-enabled payment method."""
    s = get_payment_settings()
    methods = []
    if s.enable_upi and s.active_upi_id:
        methods.append(("upi", _PAYMENT_METHOD_LABELS["upi"]))
    if s.enable_bank:
        methods.append(("bank_transfer", _PAYMENT_METHOD_LABELS["bank_transfer"]))
    if s.enable_cash:
        methods.append(("cash", _PAYMENT_METHOD_LABELS["cash"]))
    return methods


# --- UPI QR --------------------------------------------------------------------

def upi_payment_uri(amount=None, note: str | None = None) -> str | None:
    """Build a UPI intent URI from the configured id (or None when UPI is off)."""
    s = get_payment_settings()
    if not (s.enable_upi and s.active_upi_id):
        return None
    params = [f"pa={quote(s.active_upi_id)}", f"pn={quote(s.account_holder or 'Student Bazaar')}", "cu=INR"]
    if amount:
        params.append(f"am={amount}")
    if note:
        params.append(f"tn={quote(note)}")
    return "upi://pay?" + "&".join(params)


def upi_qr_data_uri(amount=None, note: str | None = None) -> str | None:
    """A base64 PNG data-URI QR for the UPI URI, generated on the fly. Returns
    ``None`` when UPI is disabled or QR generation is unavailable — the page then
    simply hides the QR."""
    uri = upi_payment_uri(amount=amount, note=note)
    if not uri:
        return None
    try:
        import qrcode  # optional dependency; degrade gracefully if absent
        img = qrcode.make(uri)
        buffer = io.BytesIO()
        img.save(buffer, format="PNG")
        return "data:image/png;base64," + base64.b64encode(buffer.getvalue()).decode("ascii")
    except Exception:  # pragma: no cover - QR is a progressive enhancement
        return None


# --- Admin-uploaded QR image (overrides the generated one) ---------------------
#
# Storage/validation reuse the existing public-static upload helpers from the
# advertisement module (no duplicate upload system): ``image_validation_error``
# for the format/size check and the generalised ``_save_ad_upload`` /
# ``remove_ad_asset`` for the UUID save and cleanup, pointed at the Premium QR
# folder via config keys.

_QR_FOLDER_KEY = "PREMIUM_QR_UPLOAD_FOLDER"
_QR_URL_KEY = "PREMIUM_QR_UPLOAD_URL"


def uploaded_qr_url() -> str | None:
    """The admin-uploaded QR's public URL, or ``None`` when none is set."""
    return get_payment_settings().qr_image_path or None


def payment_qr_src(amount=None, note: str | None = None) -> str | None:
    """The QR to show on the payment page: the admin-uploaded image if present,
    otherwise the on-the-fly generated UPI QR (unchanged fallback), otherwise
    ``None``."""
    uploaded = uploaded_qr_url()
    if uploaded:
        return uploaded
    return upi_qr_data_uri(amount=amount, note=note)


def qr_validation_error(file) -> str | None:
    """Reuse the shared image validation (format + size). ``None`` means valid."""
    from app.admin.advertisements.services import image_validation_error
    return image_validation_error(file)


def set_payment_qr(file) -> None:
    """Validate and store an uploaded QR image, replacing any previous one.

    Raises :class:`ValueError` for an invalid/oversized/missing file. The old file
    is deleted only after the new path is committed, so a failure never orphans the
    current QR. Per-request config cache is invalidated so the change is live
    immediately (no restart).
    """
    if file is None or not getattr(file, "filename", ""):
        raise ValueError("Choose a QR image to upload.")
    error = qr_validation_error(file)
    if error:
        raise ValueError(error)

    from app.admin.advertisements.services import _save_ad_upload, remove_ad_asset

    settings = get_payment_settings()
    old = settings.qr_image_path
    settings.qr_image_path = _save_ad_upload(file, _QR_FOLDER_KEY, _QR_URL_KEY)
    db.session.commit()
    _invalidate()
    if old and old != settings.qr_image_path:
        remove_ad_asset(old, _QR_FOLDER_KEY, _QR_URL_KEY)  # cleanup the replaced file


def clear_payment_qr() -> None:
    """Remove the uploaded QR (deletes the asset) and revert to the generated QR."""
    from app.admin.advertisements.services import remove_ad_asset

    settings = get_payment_settings()
    old = settings.qr_image_path
    if not old:
        return
    settings.qr_image_path = None
    db.session.commit()
    _invalidate()
    remove_ad_asset(old, _QR_FOLDER_KEY, _QR_URL_KEY)


# --- Plans ---------------------------------------------------------------------

def _ensure_plans() -> None:
    if db.session.scalar(db.select(db.func.count(PremiumPlan.id))) == 0:
        for data in _DEFAULT_PLANS:
            db.session.add(PremiumPlan(**data))
        db.session.commit()


def get_plans(enabled_only: bool = False) -> list[PremiumPlan]:
    key = f"plans:{enabled_only}"
    cached = _cache_get(key)
    if cached is not None:
        return cached
    _ensure_plans()
    query = db.select(PremiumPlan)
    if enabled_only:
        query = query.where(PremiumPlan.enabled.is_(True))
    plans = db.session.scalars(query.order_by(PremiumPlan.sort_order, PremiumPlan.id)).all()
    return _cache_set(key, plans)


def get_plan_by_name(name: str | None) -> PremiumPlan | None:
    """Case-insensitive plan lookup (so legacy 'monthly'/'yearly' still resolve)."""
    if not name:
        return None
    target = name.strip().lower()
    for plan in get_plans():
        if plan.name.strip().lower() == target:
            return plan
    return None


def plan_choices(enabled_only: bool = True) -> list[tuple[str, str]]:
    """``(name, name)`` options for the request form's plan select."""
    return [(plan.name, plan.name) for plan in get_plans(enabled_only=enabled_only)]


def plan_lookup() -> dict[str, dict]:
    """``{lowercased-name: {label, price, duration_days}}`` for admin views that
    resolve a stored plan_name to its display label/price."""
    return {
        plan.name.lower(): {"label": plan.name, "price": plan.price, "duration_days": plan.duration_days}
        for plan in get_plans()
    }


def create_plan(*, name, duration_days, price, description=None, enabled=True, sort_order=0) -> PremiumPlan:
    plan = PremiumPlan(name=name.strip(), duration_days=int(duration_days), price=price,
                       description=(description or None), enabled=bool(enabled), sort_order=int(sort_order))
    db.session.add(plan)
    db.session.commit()
    _invalidate()
    return plan


def update_plan(plan: PremiumPlan, **fields) -> PremiumPlan:
    for key in ("name", "description"):
        if key in fields:
            setattr(plan, key, (fields[key] or "").strip() or (plan.name if key == "name" else None))
    if "duration_days" in fields:
        plan.duration_days = int(fields["duration_days"])
    if "price" in fields:
        plan.price = fields["price"]
    if "sort_order" in fields:
        plan.sort_order = int(fields["sort_order"])
    if "enabled" in fields:
        plan.enabled = bool(fields["enabled"])
    db.session.commit()
    _invalidate()
    return plan


# --- Features ------------------------------------------------------------------

def _ensure_features() -> None:
    if db.session.scalar(db.select(db.func.count(PremiumFeature.id))) == 0:
        for data in _DEFAULT_FEATURES:
            db.session.add(PremiumFeature(**data))
        db.session.commit()


def get_features(enabled_only: bool = True) -> list[PremiumFeature]:
    key = f"features:{enabled_only}"
    cached = _cache_get(key)
    if cached is not None:
        return cached
    _ensure_features()
    query = db.select(PremiumFeature)
    if enabled_only:
        query = query.where(PremiumFeature.enabled.is_(True))
    features = db.session.scalars(query.order_by(PremiumFeature.display_order, PremiumFeature.id)).all()
    return _cache_set(key, features)


def create_feature(*, title, description=None, icon=None, enabled=True, display_order=0) -> PremiumFeature:
    feature = PremiumFeature(title=title.strip(), description=(description or None),
                             icon=(icon or "").strip() or None, enabled=bool(enabled),
                             display_order=int(display_order))
    db.session.add(feature)
    db.session.commit()
    _invalidate()
    return feature


def update_feature(feature: PremiumFeature, **fields) -> PremiumFeature:
    if "title" in fields:
        feature.title = (fields["title"] or "").strip() or feature.title
    if "description" in fields:
        feature.description = (fields["description"] or "").strip() or None
    if "icon" in fields:
        feature.icon = (fields["icon"] or "").strip() or None
    if "display_order" in fields:
        feature.display_order = int(fields["display_order"])
    if "enabled" in fields:
        feature.enabled = bool(fields["enabled"])
    db.session.commit()
    _invalidate()
    return feature
