import random
from decimal import Decimal
from uuid import UUID

from django.contrib.auth.hashers import make_password
from django.core.management.base import BaseCommand, CommandError
from django.db import transaction

from apps.accounts.models import User
from apps.business.distributor.models import DistributorID
from apps.business.distributor.services.distributor_service import create_distributor_id
from apps.business.products.models import Product
from apps.business.rewards.models import DistributorLevelProgress
from apps.business.schemes.models import OpportunityBundleLevel
from apps.business.wallet.models import Wallet, WalletLedger


FIRST_NAMES = [
    "Aarav", "Vihaan", "Anaya", "Ishaan", "Diya", "Advik", "Aanya", "Kabir", "Meera", "Riya",
    "Arjun", "Saanvi", "Vivaan", "Myra", "Reyansh", "Kiara", "Atharv", "Sara", "Krish", "Siya",
]

LAST_NAMES = [
    "Sharma", "Verma", "Nair", "Reddy", "Patel", "Singh", "Mehta", "Iyer", "Gupta", "Rao",
    "Jain", "Mishra", "Das", "Pillai", "Yadav", "Kapoor", "Chopra", "Bose", "Menon", "Khan",
]


class Command(BaseCommand):
    help = "Seed large-scale users + distributor purchases for full UI testing"

    def add_arguments(self, parser):
        parser.add_argument("--users", type=int, default=50000, help="Number of new users to create")
        parser.add_argument("--batch-size", type=int, default=1000, help="Bulk insert batch size")
        parser.add_argument("--pin", type=str, default="123456", help="Login PIN for generated users")
        parser.add_argument(
            "--anchor-mobiles",
            nargs="+",
            default=["8668192080", "8096248999"],
            help="Existing mobiles used as top sponsor anchors",
        )
        parser.add_argument(
            "--seed-tag",
            type=str,
            default="seedmass",
            help="Tag for generated email domain prefix",
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Print plan only; do not write DB",
        )

    def handle(self, *args, **options):
        users_to_create = options["users"]
        batch_size = options["batch_size"]
        pin = options["pin"]
        anchor_mobiles = options["anchor_mobiles"]
        seed_tag = options["seed_tag"].strip().lower() or "seedmass"
        dry_run = options["dry_run"]

        if users_to_create <= 0:
            raise CommandError("--users must be > 0")

        products_qs = Product.objects.filter(is_active=True, opportunity_bundle__isnull=False).select_related("opportunity_bundle").order_by("opportunity_bundle_id", "id")
        products = list(products_qs)
        if not products:
            raise CommandError("No active products found with linked schemes")

        scheme_products = {}
        for p in products:
            scheme_products.setdefault(p.opportunity_bundle_id, []).append(p)

        # Ensure each opportunity_bundle has at least one product for 1/2/3/4 combinations.
        empty_scheme_ids = [sid for sid, prods in scheme_products.items() if not prods]
        if empty_scheme_ids:
            raise CommandError(f"Some schemes have no products: {empty_scheme_ids}")

        # Prepare anchor users
        anchors = list(User.objects.filter(mobile__in=anchor_mobiles).order_by("id"))
        if not anchors:
            raise CommandError(f"Anchor users not found for mobiles: {anchor_mobiles}")

        # Ensure anchor distributor IDs per opportunity_bundle exist (first product in each opportunity_bundle)
        anchor_dist_by_scheme = self._ensure_anchor_distributors(anchors, scheme_products, dry_run=dry_run)

        scheme_count = len(scheme_products)
        product_total = sum(len(v) for v in scheme_products.values())
        self.stdout.write(
            self.style.NOTICE(
                f"Plan: create {users_to_create} users across {scheme_count} schemes and {product_total} active products"
            )
        )

        if dry_run:
            self.stdout.write(self.style.WARNING("Dry run enabled. No data written."))
            return

        hashed_pin = make_password(pin)

        existing_mobile_set = set(User.objects.exclude(mobile__isnull=True).values_list("mobile", flat=True))
        existing_email_set = set(User.objects.exclude(email__isnull=True).values_list("email", flat=True))
        existing_ref_set = set(User.objects.exclude(referral_code__isnull=True).values_list("referral_code", flat=True))

        next_mobile = 7000000000
        created_users = 0

        while created_users < users_to_create:
            current_batch = min(batch_size, users_to_create - created_users)
            batch_index_offset = created_users

            new_users = []
            batch_mobiles = []

            for i in range(current_batch):
                seq = batch_index_offset + i + 1
                first = FIRST_NAMES[(seq + i) % len(FIRST_NAMES)]
                last = LAST_NAMES[(seq * 3 + i) % len(LAST_NAMES)]

                mobile = self._next_unique_mobile(existing_mobile_set, next_mobile)
                next_mobile = int(mobile) + 1
                email = self._next_unique_email(existing_email_set, seed_tag, seq)
                ref_code = self._next_referral_code(existing_ref_set, seq)

                batch_mobiles.append(mobile)
                new_users.append(
                    User(
                        first_name=first,
                        last_name=last,
                        mobile=mobile,
                        email=email,
                        auth_provider="mobile",
                        login_pin=hashed_pin,
                        is_mobile_verified=True,
                        is_email_verified=True,
                        is_onboarding_complete=True,
                        accept_terms=True,
                        referral_code=ref_code,
                    )
                )

            User.objects.bulk_create(new_users, batch_size=batch_size)

            created_batch_users = list(User.objects.filter(mobile__in=batch_mobiles).order_by("id"))
            if not created_batch_users:
                raise CommandError("Failed to fetch created users for current batch")

            self._seed_distributors_progress_and_ledgers(
                created_batch_users,
                scheme_products=scheme_products,
                anchor_dist_by_scheme=anchor_dist_by_scheme,
                start_user_seq=batch_index_offset,
                batch_size=batch_size,
            )

            created_users += len(created_batch_users)
            self.stdout.write(self.style.SUCCESS(f"Created users: {created_users}/{users_to_create}"))

        self.stdout.write(self.style.SUCCESS("Mass test data seeding completed."))

    def _ensure_anchor_distributors(self, anchors, scheme_products, dry_run=False):
        result = {}
        for opportunity_bundle_id, prods in scheme_products.items():
            result[opportunity_bundle_id] = []
            first_product = prods[0]
            for anchor in anchors:
                existing = DistributorID.objects.filter(user=anchor, product=first_product).first()
                if existing:
                    result[opportunity_bundle_id].append(existing)
                    continue
                if dry_run:
                    continue
                dist = create_distributor_id(anchor, first_product, None)
                result[opportunity_bundle_id].append(dist)
        return result

    def _seed_distributors_progress_and_ledgers(self, users, scheme_products, anchor_dist_by_scheme, start_user_seq, batch_size):
        scheme_ids = list(scheme_products.keys())

        # Fetch wallet map and create missing wallets for generated users.
        user_ids = [u.id for u in users]
        existing_wallet_user_ids = set(Wallet.objects.filter(user_id__in=user_ids).values_list("user_id", flat=True))
        missing_wallets = [Wallet(user_id=uid) for uid in user_ids if uid not in existing_wallet_user_ids]
        if missing_wallets:
            Wallet.objects.bulk_create(missing_wallets, batch_size=batch_size)

        wallet_map = {
            row["user_id"]: row["id"]
            for row in Wallet.objects.filter(user_id__in=user_ids).values("id", "user_id")
        }

        per_user_scheme_counts = {}
        created_dist_ids = []

        for idx, user in enumerate(users):
            user_seq = start_user_seq + idx
            for scheme_index, opportunity_bundle_id in enumerate(scheme_ids):
                prods = scheme_products[opportunity_bundle_id]
                combination = (user_seq + scheme_index) % 4 + 1
                picks = min(combination, len(prods))
                offset = (user_seq + scheme_index) % len(prods)

                selected = [prods[(offset + j) % len(prods)] for j in range(picks)]
                anchor_list = anchor_dist_by_scheme.get(opportunity_bundle_id, [])
                sponsor = anchor_list[(user_seq + scheme_index) % len(anchor_list)] if anchor_list else None

                for p in selected:
                    dist = create_distributor_id(user, p, sponsor)
                    created_dist_ids.append(dist.id)

                per_user_scheme_counts[(user.id, opportunity_bundle_id)] = picks

        max_position = DistributorID.objects.filter(id__in=created_dist_ids).order_by('-global_position').values_list('global_position', flat=True).first() or 0

        # Reload inserted distributors for this user set.
        dists = list(
            DistributorID.objects.filter(id__in=created_dist_ids)
            .select_related("product__opportunity_bundle")
            .order_by("id")
        )

        # Precompute cumulative required positions per opportunity_bundle level.
        levels_by_scheme = {}
        for lv in OpportunityBundleLevel.objects.filter(opportunity_bundle_id__in=scheme_ids).order_by("opportunity_bundle_id", "level_number"):
            arr = levels_by_scheme.setdefault(lv.opportunity_bundle_id, [])
            cumulative = lv.required_positions + (arr[-1]["cumulative"] if arr else 0)
            arr.append(
                {
                    "level_number": lv.level_number,
                    "reward_amount": lv.reward_amount,
                    "cumulative": cumulative,
                }
            )

        progress_rows = []
        ledger_rows = []

        for idx, d in enumerate(dists):
            positions_after = max_position - d.global_position
            scheme_levels = levels_by_scheme.get(d.product.opportunity_bundle_id, [])

            current_level = 0
            for entry in scheme_levels:
                if positions_after >= entry["cumulative"]:
                    current_level = entry["level_number"]
                else:
                    break

            progress_rows.append(
                DistributorLevelProgress(
                    distributor_id=d.id,
                    current_level=current_level,
                    last_completed_level=current_level,
                    total_positions_after=positions_after,
                )
            )

            # Create a subset of claims so UI shows both claimed and eligible states.
            if current_level <= 0:
                continue

            wallet_id = wallet_map.get(d.user_id)
            if not wallet_id:
                continue

            payable_levels = [
                e for e in scheme_levels
                if e["level_number"] <= current_level and Decimal(e["reward_amount"] or 0) > 0
            ]
            claim_levels = []
            if payable_levels and idx % 3 == 0:
                claim_levels.append(payable_levels[0]["level_number"])
            if len(payable_levels) > 1 and idx % 5 == 0:
                claim_levels.append(payable_levels[1]["level_number"])

            for level_no in claim_levels:
                level_entry = next((e for e in scheme_levels if e["level_number"] == level_no), None)
                if not level_entry:
                    continue
                amount = Decimal(level_entry["reward_amount"] or 0)
                if amount <= 0:
                    continue

                ledger_rows.append(
                    WalletLedger(
                        user_id=d.user_id,
                        wallet_id=wallet_id,
                        transaction_type=WalletLedger.TransactionType.CREDIT,
                        wallet_type=WalletLedger.WalletType.EARNINGS,
                        source_type=WalletLedger.SourceType.LEVEL_REWARD,
                        amount=amount,
                        reference_id=f"LEVEL_REWARD_{d.id}_{level_no}",
                    )
                )

        if progress_rows:
            DistributorLevelProgress.objects.bulk_create(progress_rows, batch_size=batch_size, ignore_conflicts=True)

        if ledger_rows:
            WalletLedger.objects.bulk_create(ledger_rows, batch_size=batch_size, ignore_conflicts=True)

    def _next_unique_mobile(self, existing_mobile_set, start_from):
        candidate = int(start_from)
        while True:
            if candidate > 9999999999:
                candidate = 6000000000
            mobile = str(candidate)
            if mobile not in existing_mobile_set:
                existing_mobile_set.add(mobile)
                return mobile
            candidate += 1

    def _next_unique_email(self, existing_email_set, seed_tag, seq):
        n = seq
        while True:
            email = f"{seed_tag}{n}@truewave.test"
            if email not in existing_email_set:
                existing_email_set.add(email)
                return email
            n += 1

    def _next_referral_code(self, existing_ref_set, seq):
        n = seq
        while True:
            code = f"TW{n:08d}"[-10:]
            if code not in existing_ref_set:
                existing_ref_set.add(code)
                return code
            n += 1

    def _id_mod_100(self, value):
        if value is None:
            return 0
        if isinstance(value, UUID):
            return value.int % 100
        try:
            return int(value) % 100
        except Exception:
            text = str(value).replace("-", "")
            try:
                return int(text[:8], 16) % 100
            except Exception:
                return 0

    def _build_distributor_code(self, user, product, global_position):
        # 16 chars fixed: TW + opportunity_bundle(2) + product(2) + position(8) + mobile suffix(2)
        scheme_hint = self._id_mod_100(getattr(product, "opportunity_bundle_id", None))
        product_hint = self._id_mod_100(getattr(product, "id", None))
        position_part = int(global_position) % 100000000
        mobile = (getattr(user, "mobile", "") or "")
        mobile_suffix = mobile[-2:] if len(mobile) >= 2 else "00"
        return f"TW{scheme_hint:02d}{product_hint:02d}{position_part:08d}{mobile_suffix}"
