"""
Subscription Invoice Processing Job
Processes active subscriptions when next_invoice_date is reached
Creates ordre drafts and advances subscription periods
Runs daily at 04:00
"""

import logging
from datetime import datetime, date
import json
from dateutil.relativedelta import relativedelta

from app.core.database import execute_query, get_db_connection

logger = logging.getLogger(__name__)


async def process_subscriptions():
    """
    Main job: Process subscriptions due for invoicing.
    - Find active subscriptions where next_invoice_date <= today
    - Skip subscriptions blocked for invoicing (missing asset/serial)
    - Aggregate eligible subscriptions into one ordre_draft per customer + merge key + due date + billing direction
    - Advance period_start and next_invoice_date for processed subscriptions
    """
    
    try:
        logger.info("💰 Processing subscription invoices...")
        
        # Find subscriptions due for invoicing
        query = """
            SELECT 
                s.id,
                s.sag_id,
                sg.titel AS sag_name,
                s.customer_id,
                c.name AS customer_name,
                s.product_name,
                s.billing_interval,
                s.billing_direction,
                s.advance_months,
                s.price,
                s.next_invoice_date,
                s.period_start,
                s.invoice_merge_key,
                s.billing_blocked,
                s.billing_block_reason,
                COALESCE(
                    (
                        SELECT json_agg(
                            json_build_object(
                                'id', si.id,
                                'description', si.description,
                                'quantity', si.quantity,
                                'unit_price', si.unit_price,
                                'line_total', si.line_total,
                                'product_id', si.product_id,
                                'asset_id', si.asset_id,
                                'billing_blocked', si.billing_blocked,
                                'billing_block_reason', si.billing_block_reason,
                                'period_from', si.period_from,
                                'period_to', si.period_to
                            ) ORDER BY si.id
                        )
                        FROM sag_subscription_items si
                        WHERE si.subscription_id = s.id
                    ),
                    '[]'::json
                ) as line_items
            FROM sag_subscriptions s
            LEFT JOIN sag_sager sg ON sg.id = s.sag_id
            LEFT JOIN customers c ON c.id = s.customer_id
            WHERE s.status = 'active'
              AND s.next_invoice_date <= CURRENT_DATE
            ORDER BY s.next_invoice_date, s.id
        """
        
        subscriptions = execute_query(query)
        
        if not subscriptions:
            logger.info("✅ No subscriptions due for invoicing")
            return
        
        logger.info(f"📋 Found {len(subscriptions)} subscription(s) to process")
        
        blocked_count = 0
        processed_count = 0
        error_count = 0

        grouped_subscriptions = {}
        for sub in subscriptions:
            if sub.get('billing_blocked'):
                blocked_count += 1
                logger.warning(
                    "⚠️ Subscription %s skipped due to billing block: %s",
                    sub.get('id'),
                    sub.get('billing_block_reason') or 'unknown reason'
                )
                continue

            group_key = (
                int(sub['customer_id']),
                str(sub.get('invoice_merge_key') or f"cust-{sub['customer_id']}"),
                str(sub.get('next_invoice_date')),
                str(sub.get('billing_direction') or 'forward'),
            )
            grouped_subscriptions.setdefault(group_key, []).append(sub)

        for group in grouped_subscriptions.values():
            try:
                count = await _process_subscription_group(group)
                processed_count += count
            except Exception as e:
                logger.error("❌ Failed processing subscription group: %s", e, exc_info=True)
                error_count += 1

        logger.info(
            "✅ Subscription processing complete: %s processed, %s blocked, %s errors",
            processed_count,
            blocked_count,
            error_count,
        )
    
    except Exception as e:
        logger.error(f"❌ Subscription processing job failed: {e}", exc_info=True)


async def _process_subscription_group(subscriptions: list[dict]) -> int:
    """Create one aggregated ordre draft for a group of subscriptions and advance all periods."""

    if not subscriptions:
        return 0

    first = subscriptions[0]
    customer_id = first['customer_id']
    customer_name = first.get('customer_name') or f"Customer #{customer_id}"
    billing_direction = first.get('billing_direction') or 'forward'
    invoice_aggregate_key = first.get('invoice_merge_key') or f"cust-{customer_id}"

    conn = get_db_connection()
    cursor = conn.cursor()

    try:
        ordre_lines = []
        source_subscription_ids = []
        coverage_start = None
        coverage_end = None

        for sub in subscriptions:
            subscription_id = int(sub['id'])
            source_subscription_ids.append(subscription_id)

            line_items = sub.get('line_items', [])
            if isinstance(line_items, str):
                line_items = json.loads(line_items)

            period_start = sub.get('period_start') or sub.get('next_invoice_date')
            period_end = _calculate_next_period_start(period_start, sub['billing_interval'])
            if coverage_start is None or period_start < coverage_start:
                coverage_start = period_start
            if coverage_end is None or period_end > coverage_end:
                coverage_end = period_end

            for item in line_items:
                if item.get('billing_blocked'):
                    logger.warning(
                        "⚠️ Skipping blocked subscription item %s on subscription %s",
                        item.get('id'),
                        subscription_id,
                    )
                    continue

                product_number = str(item.get('product_id', 'SUB'))
                ordre_lines.append({
                    "product": {
                        "productNumber": product_number,
                        "description": item.get('description', '')
                    },
                    "quantity": float(item.get('quantity', 1)),
                    "unitNetPrice": float(item.get('unit_price', 0)),
                    "totalNetAmount": float(item.get('line_total', 0)),
                    "discountPercentage": 0,
                    "metadata": {
                        "subscription_id": subscription_id,
                        "asset_id": item.get('asset_id'),
                        "period_from": str(item.get('period_from') or period_start),
                        "period_to": str(item.get('period_to') or period_end),
                    }
                })

        if not ordre_lines:
            logger.warning("⚠️ No invoiceable lines in subscription group for customer %s", customer_id)
            return 0

        title = f"Abonnementer: {customer_name}"
        notes = (
            f"Aggregated abonnement faktura\n"
            f"Kunde: {customer_name}\n"
            f"Coverage: {coverage_start} til {coverage_end}\n"
            f"Subscription IDs: {', '.join(str(sid) for sid in source_subscription_ids)}"
        )

        insert_query = """
            INSERT INTO ordre_drafts (
                title,
                customer_id,
                lines_json,
                notes,
                coverage_start,
                coverage_end,
                billing_direction,
                source_subscription_ids,
                invoice_aggregate_key,
                layout_number,
                created_by_user_id,
                sync_status,
                export_status_json,
                updated_at
            ) VALUES (%s, %s, %s::jsonb, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s::jsonb, CURRENT_TIMESTAMP)
            RETURNING id
        """

        cursor.execute(insert_query, (
            title,
            customer_id,
            json.dumps(ordre_lines, ensure_ascii=False),
            notes,
            coverage_start,
            coverage_end,
            billing_direction,
            source_subscription_ids,
            invoice_aggregate_key,
            1,  # Default layout
            None,  # System-created
            'pending',
            json.dumps({"source": "subscription", "subscription_ids": source_subscription_ids}, ensure_ascii=False)
        ))

        ordre_id = cursor.fetchone()[0]
        logger.info(
            "✅ Created aggregated ordre draft #%s for %s subscription(s)",
            ordre_id,
            len(source_subscription_ids),
        )

        for sub in subscriptions:
            subscription_id = int(sub['id'])
            current_period_start = sub.get('period_start') or sub.get('next_invoice_date')
            new_period_start = _calculate_next_period_start(current_period_start, sub['billing_interval'])
            new_next_invoice_date = _calculate_next_period_start(new_period_start, sub['billing_interval'])

            cursor.execute(
                """
                UPDATE sag_subscriptions
                SET period_start = %s,
                    next_invoice_date = %s,
                    updated_at = CURRENT_TIMESTAMP
                WHERE id = %s
                """,
                (new_period_start, new_next_invoice_date, subscription_id)
            )

        conn.commit()
        return len(source_subscription_ids)

    except Exception as e:
        conn.rollback()
        raise e
    finally:
        cursor.close()
        conn.close()


def _calculate_next_period_start(current_date, billing_interval: str) -> date:
    """Calculate next period start date based on billing interval"""
    
    # Parse current_date if it's a string
    if isinstance(current_date, str):
        current_date = datetime.strptime(current_date, '%Y-%m-%d').date()
    elif isinstance(current_date, datetime):
        current_date = current_date.date()
    
    # Calculate delta based on interval
    if billing_interval == 'daily':
        delta = relativedelta(days=1)
    elif billing_interval == 'biweekly':
        delta = relativedelta(weeks=2)
    elif billing_interval == 'monthly':
        delta = relativedelta(months=1)
    elif billing_interval == 'quarterly':
        delta = relativedelta(months=3)
    elif billing_interval == 'yearly':
        delta = relativedelta(years=1)
    else:
        # Default to monthly if unknown
        logger.warning(f"Unknown billing interval '{billing_interval}', defaulting to monthly")
        delta = relativedelta(months=1)
    
    next_date = current_date + delta
    return next_date
