# utilities/utils.py
from decimal import Decimal
from django.utils import timezone
from django.utils.timezone import now
from recharge.services.provider_factory import get_recharge_client
from recharge.models import Operator, Circle, RechargeTransaction
from payments.models import RechargePaymentSummary
from payments.ids import generate_unique_provider_txnid
from recharge.services.provider_utils import get_provider_key, get_source_value
from recharge.services.goterpay_api import GoterPayAPI
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
from django.db import transaction
from datetime import datetime
from recharge.services.recharge_flow import handle_recharge_outcome

TWOPL = Decimal("0.01")
def _d(x):
    try: return Decimal(str(x))
    except (InvalidOperation, ValueError, TypeError): return Decimal("0")
def q2(x: Decimal) -> Decimal: return _d(x).quantize(TWOPL, rounding=ROUND_HALF_UP)
def to_paise(x: Decimal) -> int: return int((q2(x) * 100).quantize(Decimal("1"), rounding=ROUND_HALF_UP))

def _extract_bill_number(service: str, fields: dict) -> str:
    s = (service or "").lower()
    if s == "fastag":
        return str(fields.get("vehicle_number", "")).strip()
    if s == "dth":
        return str(fields.get("subscriber_id", "")).strip()
    if s == "landline":
        return (str(fields.get("std_code", "")) + str(fields.get("phone_no", ""))).strip()
    if s in ("lpg", "gas"):
        # match your UI: "book_using" -> Mobile Number | Consumer ID
        using = (fields.get("book_using") or "").strip().lower()
        if using == "consumer id":
            return str(fields.get("consumer_id", "")).strip()
        return str(fields.get("mobile", "")).strip()
    # electricity / postpaid / etc. — pick the same key you used for BillFetch
    for key in ("consumer_number", "account_number", "account_no","ca_number","mobile","number","connection_id","account_id", "consumer_no"):
        v = fields.get(key)
        if v:
            return str(v).strip()
    return ""

def _parse_req_time(s: str | None):
    if not s:
        return None
    try:
        # Example: "2024-09-25 23:36:18"
        return datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
    except Exception:
        return None
    
def fmt2(x) -> str:
    """Always return a string with two decimals, even if input was str/None."""
    return format(q2(x), ".2f")

import logging
log = logging.getLogger(__name__)

def initiate_bill_payment(
    *,
    user,
    service: str,
    provider: str,
    fields: dict,
    amount,                              # rupees string/Decimal
    order_id: str,
    use_ext: Decimal = Decimal("0.00"),
    use_int: Decimal = Decimal("0.00"),
    gateway_ref: str | None = None,
    client_txn_id: str | None = None,
    amount_paise: int | None = None,     # NEW (preferred for providers)
    razorpay_order_id: str | None = None,
    razorpay_payment_id: str | None = None,
):
    provider_key = get_provider_key()
    source_value = get_source_value()
    vendor_code  = source_value
    # 🔒 Single source of truth for client txn id
    client_txn_id = client_txn_id or order_id

    # log.info("bill fields keys=%s", sorted(list(fields.keys())) if isinstance(fields, dict) else type(fields))
    number = _extract_bill_number(service, fields)
    # --- normalize money ---
    amount_rupees = q2(_d(amount))
    if amount_paise is None:
        amount_paise = to_paise(amount_rupees)
    use_ext = q2(_d(use_ext))
    use_int = q2(_d(use_int))
    paid_via_gateway = q2(amount_rupees - use_ext - use_int)

    client = get_recharge_client()
    try:
        # If provider needs integer paise, switch to the commented line:
        api_resp = client.bill_pay(
            txnid=client_txn_id,
            number=str(number),
            amount=f"{amount_rupees:.2f}",        # exact rupees string (2dp)
            # amount=str(amount_paise),           # <- use this if provider expects paise
            operator_code=str(provider),
        )
    except Exception as e:
        api_resp = {"status": "FAILED", "resText": f"API Error: {e}", "TxnId": ""}

    # --- normalize status/message ---

    raw_status     = (api_resp.get("status") or "").strip().upper()
    status_title   = GoterPayAPI.normalize_status(raw_status).title()  # Success/Failure/Pending/Error
    status_message = (api_resp.get("resText") or api_resp.get("message") or "").strip()
    if len(status_message) > 2000: status_message = status_message[:2000] + "…"

    # provider refs (try common keys)
    # provider refs (try common keys)
    provider_order_id = api_resp.get("optId") or ""
    client_txn_id = api_resp.get("TxnId") or ""

    # Extract provider extras (with safe fallbacks)
    operator_order_id = api_resp.get("RefId") or ""
    provider_service = api_resp.get("Service") or ""
    provider_commission = api_resp.get("Comi") or ""
    provider_req_time_s = api_resp.get("reqTime") or ""
    provider_voucher_cd = api_resp.get("VoucherCode") or ""
    provider_recharge_note = api_resp.get("resText") or ""

    # Normalize commission to Decimal(3)
    try:
        provider_commission_dec = Decimal(str(provider_commission))
    except Exception:
        provider_commission_dec = None

    provider_req_dt = _parse_req_time(provider_req_time_s)

    # --- Payment Summary (keep Decimal fields if your model supports it) ---
    RechargePaymentSummary.objects.update_or_create(
        order_id=order_id,
        defaults={
            "user": user,
            "recharge_amount": amount_rupees,            # Summary keeps FloatField today
            "used_external_wallet": use_ext,
            "used_internal_wallet": use_int,
            "paid_via_gateway": paid_via_gateway,

            "recharge_status": status_title,
            "gateway_reference": (razorpay_payment_id or razorpay_order_id or provider_order_id or client_txn_id),
            "status_message": status_message or provider_recharge_note,
            "full_response": api_resp,
            # NEW fields
            "provider_order_id" : provider_order_id,
            "operator_order_id": operator_order_id,
            "provider_service": provider_service,
            "provider_commission": provider_commission_dec,
            "provider_request_time": provider_req_dt,
            "provider_voucher_code": provider_voucher_cd,
        }
    )

    # --- Transaction row: idempotent upsert by order_id ---
    # try to resolve operator/circle, but don't fail if missing

    tx_defaults = dict(
        user=user,
        number=number,
        amount=amount_rupees,
        client_txn_id=client_txn_id,
        provider_order_id=provider_order_id,
        status=status_title,
        status_message=status_message,
        response_data=api_resp,
        status_updated_at=timezone.now(),
        used_ext_wallet=use_ext,
        used_viralpe_wallet=use_int,
        paid_via_gateway=paid_via_gateway,
        razorpay_order_id=razorpay_order_id,
        razorpay_payment_id=razorpay_payment_id,
        service_type=service,
    )
    try:
        tx_defaults["operator"] = Operator.objects.get(code=provider, source__iexact=source_value)
    except Operator.DoesNotExist:
        pass
    try:
        tx_defaults["circle"] = Circle.objects.get(code="NA", source__iexact=source_value)
    except Circle.DoesNotExist:
        pass
    # log.info(order_id)
    # log.info(tx_defaults)

    with transaction.atomic():
        tx, _created = RechargeTransaction.objects.update_or_create(
            order_id=order_id,
            defaults=tx_defaults
        )
    # transaction = RechargeTransaction.objects.create(**tx_kwargs)
    # --- Outcome hooks ---
    if status_title.lower() == "success":
        handle_recharge_outcome(
            user=user,
            transaction=tx,
            order_id=order_id,
            status_title="success",
            status_message=tx.status_message,
            amount=tx.amount,
            operator=provider,
            vendor_code=vendor_code,
            gateway_ref=(tx.razorpay_order_id or provider_order_id),
            paid_via_gateway=tx.paid_via_gateway,
            service=service,
            provider_commission=provider_commission_dec or None
        )
    elif status_title.lower() in ("failure", "error"):
        # Trigger VP-wallet refund immediately (same policy as recharge)
        handle_recharge_outcome(
            user=user,
            transaction=tx,
            order_id=order_id,
            status_title="failure",
            status_message=tx.status_message,
            amount=tx.amount,
            operator=provider,
            vendor_code=vendor_code,
            gateway_ref=(tx.razorpay_order_id or provider_order_id),
            paid_via_gateway=tx.paid_via_gateway,
            service=service,
            provider_commission=provider_commission_dec or None
        )
    # Pending is handled by your poller
    return {
        "status": status_title,
        "order_id": order_id,
        "client_txn_id": client_txn_id,          # same as order_id
        "provider_order_id": provider_order_id,
        "message": status_message,
        "raw": api_resp,
        "amount": fmt2(amount),
        "used_external_wallet": fmt2(use_ext),
        "used_internal_wallet": fmt2(use_int),
        "paid_via_gateway": fmt2(paid_via_gateway),
        "razorpay_order_id": razorpay_order_id,
        "razorpay_payment_id": razorpay_payment_id,

    }


