# subscriptions/services.py
"""
SaaS subscription business logic.
All side-effects are isolated here -- views just call these functions.
Supports Monnify and Paystack gateways -- active gateway set by superadmin.
"""
import logging
from datetime import date, timedelta
from decimal import Decimal

from django.conf import settings
from django.db import transaction as db_transaction
from django.utils import timezone

from companies.models import Company, Plan
from .models import (
    Subscription, PaymentTransaction, SaasPromoCode,
    BILLING_CYCLE_DAYS,
)

logger = logging.getLogger(__name__)

TRIAL_DAYS = getattr(settings, 'TRIAL_DURATION_DAYS', 3)


# -----------------------------------------------------------
# TRIAL
# -----------------------------------------------------------

def create_trial_subscription(company):
    """Create a 3-day trial subscription for a newly registered company."""
    Subscription.objects.filter(company=company, is_active=True).update(is_active=False)

    trial_plan = (
        Plan.objects.filter(is_active=True, name__icontains='trial').first()
        or Plan.objects.filter(is_active=True, price_monthly=0).first()
        or Plan.objects.filter(is_active=True).order_by('display_order', 'price_monthly').first()
    )

    if not trial_plan:
        raise ValueError("No active plans found. Please create at least one plan in admin.")

    today = date.today()
    expiry = today + timedelta(days=TRIAL_DAYS)

    sub = Subscription.objects.create(
        company=company,
        plan=trial_plan,
        billing_cycle='monthly',
        start_date=today,
        expiry_date=expiry,
        is_trial=True,
        is_active=True,
        payment_status='trial',
    )
    _sync_company(company, trial_plan, expiry)
    return sub


# -----------------------------------------------------------
# PLAN PRICE CALCULATION
# -----------------------------------------------------------

def calculate_subscription_amount(plan, billing_cycle):
    return Decimal(str(plan.get_price_for_cycle(billing_cycle)))


def validate_promo_code(code, plan, amount):
    if not code:
        return None, Decimal('0.00')

    try:
        promo = SaasPromoCode.objects.get(code=code.upper().strip())
    except SaasPromoCode.DoesNotExist:
        raise ValueError("Invalid promo code.")

    if not promo.is_valid:
        if promo.is_expired:
            raise ValueError("This promo code has expired.")
        elif promo.is_exhausted:
            raise ValueError("This promo code has reached its usage limit.")
        elif promo.is_not_yet_valid:
            raise ValueError("This promo code is not yet active.")
        raise ValueError("This promo code is no longer valid.")

    if promo.allowed_plans.exists() and not promo.allowed_plans.filter(pk=plan.pk).exists():
        raise ValueError("This promo code is not valid for the selected plan.")

    discount = promo.calculate_discount(amount)
    return promo, discount


# -----------------------------------------------------------
# PAYMENT INTENT (gateway-agnostic)
# -----------------------------------------------------------

def _get_active_gateway():
    """Return the active gateway slug chosen by superadmin."""
    try:
        from core.models import SiteConfiguration
        cfg = SiteConfiguration.get_solo()
        gw = cfg.active_payment_gateway
        if gw == 'both':
            return 'monnify'  # default when 'both' -- can be extended per-company later
        return gw
    except Exception:
        return 'monnify'


def _get_manual_bank_details():
    """Return bank transfer details set by superadmin, or empty dict."""
    try:
        from core.models import SiteConfiguration
        cfg = SiteConfiguration.get_solo()
        return {
            'bank_name':    cfg.manual_bank_name,
            'account_number': cfg.manual_account_number,
            'account_name': cfg.manual_account_name,
            'instructions': cfg.manual_payment_instructions,
            'whatsapp_number': cfg.manual_whatsapp_number,
        }
    except Exception:
        return {}


def create_payment_intent(company, plan, billing_cycle,
                          promo_code_str='', redirect_url='',
                          use_frontend_sdk=False):
    """
    Calculate amount, validate promo, then initialise the active payment gateway.
    Gateway determined by SiteConfiguration.active_payment_gateway (superadmin control).
    Returns dict: { transaction, checkout_url, promo_obj, gateway }
    """
    base_amount = calculate_subscription_amount(plan, billing_cycle)
    promo_obj, discount = validate_promo_code(promo_code_str, plan, base_amount)
    final_amount = max(base_amount - discount, Decimal('0.00'))
    gateway = _get_active_gateway()

    # Resolve customer details
    admin_user = company.users.filter(role='admin').first()
    customer_email = (
        (admin_user.email if admin_user else None)
        or company.email
        or getattr(settings, 'ADMIN_EMAIL', getattr(settings, 'DEFAULT_FROM_EMAIL', 'admin@domain.com'))
    )
    customer_name = company.name

    # Create gateway-agnostic pending transaction
    txn = PaymentTransaction.objects.create(
        company=company,
        plan=plan,
        billing_cycle=billing_cycle,
        amount=base_amount,
        discount_amount=discount,
        final_amount=final_amount,
        promo_code_used=promo_code_str.upper().strip() if promo_code_str else '',
        status='pending',
        gateway=gateway,
    )

    # Free plan -- bypass gateway entirely
    if final_amount <= 0:
        txn.status = 'paid'
        txn.gateway_reference = f"FREE_{txn.payment_reference}"
        txn.payment_method = 'FREE'
        txn.save(update_fields=['status', 'gateway_reference', 'payment_method'])
        activate_subscription_for_transaction(txn)
        sep = '&' if '?' in redirect_url else '?'
        checkout_url = f"{redirect_url}{sep}paymentReference={txn.payment_reference}"
        return {'transaction': txn, 'checkout_url': checkout_url, 'promo_obj': promo_obj, 'gateway': 'free'}

    # Manual bank transfer -- no API call; client transfers money offline
    if gateway == 'manual':
        bank_details = _get_manual_bank_details()
        return {
            'transaction':  txn,
            'checkout_url': '',
            'promo_obj':    promo_obj,
            'gateway':      'manual',
            'bank_details': bank_details,
        }

    # AJAX / frontend SDK mode: return data, let JS call gateway
    if use_frontend_sdk:
        return {'transaction': txn, 'checkout_url': '', 'promo_obj': promo_obj, 'gateway': gateway}

    # -- Route to active gateway --------
    checkout_url = ''

    if gateway == 'paystack':
        from . import paystack as paystack_client
        try:
            data = paystack_client.initialize_transaction(
                amount_naira=final_amount,
                email=customer_email,
                reference=txn.payment_reference,
                callback_url=redirect_url,
                metadata={'company': company.slug, 'plan': plan.name, 'cycle': billing_cycle},
            )
            txn.gateway_reference = data.get('access_code', '')
            txn.save(update_fields=['gateway_reference'])
            checkout_url = data.get('authorization_url', '')
        except Exception as e:
            logger.error(f"Paystack init failed for {company.slug}: {e}")
            txn.status = 'failed'
            txn.save(update_fields=['status'])
            raise ValueError(f"Payment gateway error: {e}")

    else:  # monnify (default)
        from . import monnify as monnify_client
        try:
            monnify_data = monnify_client.initialize_transaction(
                amount=float(final_amount),
                reference=txn.payment_reference,
                customer_email=customer_email,
                customer_name=customer_name,
                description=f"SwiftPOS {plan.name} - {billing_cycle}",
                redirect_url=redirect_url,
            )
            txn.gateway_reference = monnify_data.get('transactionReference', '')
            # Keep backward compat field too
            txn.monnify_reference = monnify_data.get('transactionReference', '')
            txn.save(update_fields=['gateway_reference', 'monnify_reference'])
            checkout_url = monnify_data.get('checkoutUrl', '')
        except Exception as e:
            logger.error(f"Monnify init failed for {company.slug}: {e}")
            txn.status = 'failed'
            txn.save(update_fields=['status'])
            raise ValueError(f"Payment gateway error: {e}")

    return {'transaction': txn, 'checkout_url': checkout_url, 'promo_obj': promo_obj, 'gateway': gateway}


# -----------------------------------------------------------
# ACTIVATE SUBSCRIPTION
# -----------------------------------------------------------

@db_transaction.atomic
def activate_subscription_for_transaction(txn):
    """
    After confirmed payment: mark paid, create subscription, sync company.
    Works for both Monnify and Paystack transactions.
    """
    if txn.status == 'paid':
        return txn.subscription

    today = date.today()
    txn.status = 'paid'
    txn.paid_at = timezone.now()
    txn.save(update_fields=['status', 'paid_at'])

    company = txn.company
    plan = txn.plan
    billing_cycle = txn.billing_cycle

    old_active_subs = Subscription.objects.filter(company=company, is_active=True)
    current_sub = old_active_subs.first()

    start_date_for_new_sub = today
    if current_sub and current_sub.expiry_date and current_sub.expiry_date >= today:
        start_date_for_new_sub = current_sub.expiry_date

    expiry = start_date_for_new_sub + timedelta(days=BILLING_CYCLE_DAYS[billing_cycle])
    old_active_subs.update(is_active=False)

    sub = Subscription.objects.create(
        company=company,
        plan=plan,
        billing_cycle=billing_cycle,
        start_date=today,
        expiry_date=expiry,
        is_trial=False,
        is_active=True,
        payment_status='paid',
    )

    txn.subscription = sub
    txn.save(update_fields=['subscription'])

    _sync_company(company, plan, expiry)

    if txn.promo_code_used:
        SaasPromoCode.objects.filter(code=txn.promo_code_used).update(
            usage_count=models_F('usage_count') + 1
        )

    logger.info(f"Subscription activated: company={company.slug}, plan={plan.name}, expiry={expiry}")
    return sub


def models_F(field):
    from django.db.models import F
    return F(field)


# -----------------------------------------------------------
# UPGRADE / DOWNGRADE
# -----------------------------------------------------------

def upgrade_plan(company, new_plan, billing_cycle, promo_code_str='', redirect_url=''):
    return create_payment_intent(company, new_plan, billing_cycle, promo_code_str, redirect_url)


def schedule_downgrade(company, new_plan):
    sub = Subscription.objects.filter(company=company, is_active=True).first()
    if not sub:
        raise ValueError("No active subscription found.")
    sub.pending_downgrade_plan = new_plan
    sub.save(update_fields=['pending_downgrade_plan'])
    logger.info(f"Downgrade scheduled: company={company.slug}, new_plan={new_plan.name}")
    return sub


# -----------------------------------------------------------
# INTERNAL SYNC
# -----------------------------------------------------------

def _sync_company(company, plan, expiry_date):
    company.plan = plan
    company.expiry_date = expiry_date
    company.is_active = True
    company.save(update_fields=['plan', 'expiry_date', 'is_active'])
