"""Management command to add or deduct user wallet points."""

from __future__ import annotations

import json
from decimal import Decimal

from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.db.models import Q

from apps.users.models import User
from apps.transactions.models import Transaction
from utils.wallet import get_wallet_service
from django_wallet_utils.exceptions import (
    InsufficientBalanceError,
    InvalidPointTypeError,
    InvalidParamsError,
)
from django_wallet_utils.transaction_types import (
    get_transaction_type,
    get_transaction_type_name,
    WALLET_DEPOSIT,
    WALLET_WITHDRAW,
)


class Command(BaseCommand):
    help = (
        "Add or deduct points from a user's wallet. "
        "Supports all wallet-utils options including transaction types, remarks, and extra data."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "user_id",
            type=int,
            help="User ID (telegram_user_id) or database ID",
        )
        parser.add_argument(
            "amount",
            type=str,
            help="Amount to add/deduct (supports decimal values, e.g., '100.50')",
        )
        parser.add_argument(
            "operation",
            type=str,
            choices=["add", "deduct", "credit", "debit"],
            help="Operation type: 'add'/'credit' to add points, 'deduct'/'debit' to deduct points",
        )
        parser.add_argument(
            "--transaction-type",
            type=str,
            dest="trans_type",
            help=(
                "Transaction type: integer constant (e.g., 1006) or name in kebab-case (e.g., 'wallet-topup'). "
                "Also accepts UPPER_SNAKE_CASE constants (e.g., 'WALLET_TOPUP') which are auto-converted. "
                "Defaults to WALLET_DEPOSIT (1000) for add, WALLET_WITHDRAW (1003) for deduct. "
                "Examples: 'wallet-topup', 'WALLET_TOPUP', '1006'"
            ),
        )
        parser.add_argument(
            "--remarks",
            type=str,
            default="",
            help="Remarks/description for the transaction",
        )
        parser.add_argument(
            "--initiator-id",
            type=int,
            dest="iid",
            default=-100,
            help=(
                "Initiator ID (user who performed this transaction). "
                "Use -100 for system-initiated (default). Use positive user ID for user-initiated."
            ),
        )
        parser.add_argument(
            "--allow-negative",
            action="store_true",
            dest="allow_negative",
            help="Allow negative balance (only applies to deduct operations)",
        )
        parser.add_argument(
            "--point-type",
            type=str,
            dest="point_type",
            default="credit_balance",
            help="Point type to operate on. Options: credit_balance (default), bonus_balance",
        )
        parser.add_argument(
            "--extra-data",
            type=str,
            dest="extra_data",
            help="Extra data as JSON string (e.g., '{\"reason\": \"manual adjustment\"}')",
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Show what would be done without actually executing the transaction",
        )

    def handle(self, *args, **options):
        user_id = options["user_id"]
        amount_str = options["amount"]
        operation = options["operation"]
        trans_type_str = options.get("trans_type")
        remarks = options.get("remarks", "")
        iid = options.get("iid", -100)
        allow_negative = options.get("allow_negative", False)
        point_type = options.get("point_type", "credit_balance")
        extra_data_str = options.get("extra_data")
        dry_run = options.get("dry_run", False)

        # Normalize operation
        is_add = operation in ("add", "credit")

        # Parse amount
        try:
            amount = Decimal(amount_str)
            if amount <= 0:
                raise CommandError(f"Amount must be positive, got: {amount}")
        except (ValueError, TypeError) as e:
            raise CommandError(f"Invalid amount format: {amount_str}. Error: {e}")

        # Parse transaction type
        trans_type = self._parse_transaction_type(trans_type_str, is_add)

        # Parse extra data
        extra_data = self._parse_extra_data(extra_data_str)

        # Get user
        try:
            user = User.objects.get(
                Q(pk=user_id) | Q(telegram_user_id=user_id)
            )
        except User.DoesNotExist:
            raise CommandError(f"User not found: {user_id}")
        except User.MultipleObjectsReturned:
            raise CommandError(
                f"Multiple users found for ID {user_id}. Please use a more specific identifier."
            )

        # Get current balance
        current_balance = getattr(user, point_type, None)
        if current_balance is None:
            raise CommandError(
                f"User model does not have point type '{point_type}'. "
                f"Available point types: credit_balance, bonus_balance"
            )

        # Get wallet service
        wallet_service = get_wallet_service()

        # Prepare params
        params = {}
        if extra_data:
            params["data"] = extra_data

        # Show what will be done
        self.stdout.write("\n" + "=" * 60)
        self.stdout.write(self.style.WARNING("Transaction Details:"))
        self.stdout.write("=" * 60)
        self.stdout.write(f"User: {user.first_name} (ID: {user.id}, Telegram: {user.telegram_user_id})")
        self.stdout.write(f"Operation: {operation.upper()}")
        self.stdout.write(f"Point Type: {point_type}")
        self.stdout.write(f"Amount: {amount}")
        self.stdout.write(f"Current Balance: {current_balance}")
        
        if is_add:
            new_balance = current_balance + amount
        else:
            new_balance = current_balance - amount
            if new_balance < 0 and not allow_negative:
                self.stdout.write(
                    self.style.ERROR(
                        f"\n⚠️  WARNING: This would result in negative balance: {new_balance}"
                    )
                )
        
        self.stdout.write(f"New Balance: {new_balance}")
        try:
            trans_type_name = get_transaction_type_name(trans_type)
            self.stdout.write(f"Transaction Type: {trans_type} ({trans_type_name})")
        except (ValueError, KeyError):
            self.stdout.write(f"Transaction Type: {trans_type} (custom)")
        self.stdout.write(f"Initiator ID: {iid}")
        self.stdout.write(f"Remarks: {remarks or '(none)'}")
        if extra_data:
            self.stdout.write(f"Extra Data: {json.dumps(extra_data, indent=2)}")
        if allow_negative:
            self.stdout.write("Allow Negative: Yes")
        self.stdout.write("=" * 60 + "\n")

        if dry_run:
            self.stdout.write(self.style.WARNING("DRY RUN: No changes made."))
            return

        # Confirm if deducting and balance would go negative
        if not is_add and new_balance < 0 and not allow_negative:
            raise CommandError(
                f"Insufficient balance. Current: {current_balance}, Required: {amount}, "
                f"Shortfall: {abs(new_balance)}. Use --allow-negative to allow negative balance."
            )

        # Execute transaction
        try:
            with transaction.atomic():
                if is_add:
                    transaction_id = wallet_service.add_point(
                        user_id=user.id,
                        point_type=point_type,
                        amount=amount,
                        remarks=remarks,
                        trans_type=trans_type,
                        iid=iid,
                        params=params if params else None,
                    )
                    operation_name = "Added"
                else:
                    transaction_id = wallet_service.deduct_point(
                        user_id=user.id,
                        point_type=point_type,
                        amount=amount,
                        remarks=remarks,
                        trans_type=trans_type,
                        allow_negative=allow_negative,
                        iid=iid,
                        params=params if params else None,
                    )
                    operation_name = "Deducted"

                # Refresh user to get updated balance
                user.refresh_from_db()
                new_balance = getattr(user, point_type)

                self.stdout.write(
                    self.style.SUCCESS(
                        f"\n✅ Successfully {operation_name.lower()} {amount} {point_type} points."
                    )
                )
                self.stdout.write(f"Transaction ID: {transaction_id}")
                self.stdout.write(f"New Balance: {new_balance}")

                # Create Django Transaction record for API visibility
                # Map operation to Transaction types
                if is_add:
                    tx_type = Transaction.TYPE_DEPOSIT
                else:
                    tx_type = Transaction.TYPE_WITHDRAWAL

                # Create the transaction record
                tx = Transaction.objects.create(
                    user=user,
                    transaction_type=tx_type,
                    amount=amount,
                    status=Transaction.STATUS_COMPLETED,
                    balance_before=current_balance,
                    balance_after=new_balance,
                    data={
                        "wallet_transaction_id": transaction_id,
                        "wallet_transaction_type": trans_type,
                        "transaction_type_name": get_transaction_type_name(trans_type) if isinstance(trans_type, int) else str(trans_type),
                        "initiator_id": iid,
                        "remarks": remarks,
                        "extra_data": extra_data,
                        "manual_operation": True
                    }
                )
                self.stdout.write(f"Created API Transaction record: ID {tx.id}")

        except InsufficientBalanceError as e:
            raise CommandError(
                f"Insufficient balance. Available: {e.available}, Required: {e.requested}"
            )
        except InvalidPointTypeError as e:
            raise CommandError(f"Invalid point type: {e.point_type}")
        except InvalidParamsError as e:
            raise CommandError(f"Invalid parameters in {e.method}: {e}")
        except Exception as e:
            raise CommandError(f"Error executing transaction: {e}")

    def _parse_transaction_type(self, trans_type_str: str | None, is_add: bool) -> int:
        """
        Parse transaction type from string or use default.
        
        Supports multiple formats:
        - Integer: "1006" → 1006
        - Kebab-case: "wallet-topup" → 1006
        - UPPER_SNAKE_CASE: "WALLET_TOPUP" → "wallet-topup" → 1006
        """
        if trans_type_str is None:
            # Use defaults based on operation
            return WALLET_DEPOSIT if is_add else WALLET_WITHDRAW

        # Try to parse as integer first
        try:
            return int(trans_type_str)
        except ValueError:
            pass

        # Normalize the string: convert UPPER_SNAKE_CASE to kebab-case
        normalized_name = self._normalize_transaction_type_name(trans_type_str)

        # Try to get by normalized name
        try:
            return get_transaction_type(normalized_name)
        except (ValueError, KeyError):
            # Try original name in case it's already in kebab-case
            if normalized_name != trans_type_str:
                try:
                    return get_transaction_type(trans_type_str)
                except (ValueError, KeyError):
                    pass
            
            raise CommandError(
                f"Invalid transaction type: {trans_type_str}. "
                f"Use an integer constant (e.g., 1006), kebab-case name (e.g., 'wallet-topup'), "
                f"or UPPER_SNAKE_CASE constant (e.g., 'WALLET_TOPUP')."
            )

    def _normalize_transaction_type_name(self, name: str) -> str:
        """
        Normalize transaction type name from UPPER_SNAKE_CASE to kebab-case.
        
        Examples:
        - "WALLET_TOPUP" → "wallet-topup"
        - "wallet-topup" → "wallet-topup" (no change)
        - "WALLET_DEPOSIT_CANCEL" → "wallet-deposit-cancel"
        """
        # If it's already lowercase with dashes, return as-is
        if name.islower() and "-" in name:
            return name
        
        # Convert UPPER_SNAKE_CASE to kebab-case
        # WALLET_TOPUP → wallet-topup
        normalized = name.lower().replace("_", "-")
        return normalized

    def _parse_extra_data(self, extra_data_str: str | None) -> dict | None:
        """Parse extra data from JSON string."""
        if not extra_data_str:
            return None

        try:
            return json.loads(extra_data_str)
        except json.JSONDecodeError as e:
            raise CommandError(f"Invalid JSON in --extra-data: {e}")
