import json
from decimal import Decimal

from django.contrib.auth import get_user_model
from rest_framework import serializers

from admin_panel.models import Investor, Investment, Business, Scheme
from admin_panel.serializers import InvestorSerializer, InvestorInvestmentSerializer, SchemeSerializer
from .models import Agent, AgentDetailField, AgentDocument, AgentInvestmentShare, AgentLedgerEntry, AgentPayout

User = get_user_model()


class AgentDocumentSerializer(serializers.ModelSerializer):
    class Meta:
        model = AgentDocument
        fields = ("id", "section", "title", "file", "uploaded_by", "created_at")
        read_only_fields = ("uploaded_by", "created_at")


class AgentSerializer(serializers.ModelSerializer):
    login_username = serializers.CharField(write_only=True, required=True)
    login_password = serializers.CharField(write_only=True, required=True, min_length=8)
    login_username_display = serializers.SerializerMethodField()
    login_password_status = serializers.SerializerMethodField()
    login_password_display = serializers.SerializerMethodField()
    identity_details = serializers.JSONField(write_only=True, required=False)
    bank_details = serializers.JSONField(write_only=True, required=False)
    nominee_details = serializers.JSONField(write_only=True, required=False)
    identity_fields = serializers.SerializerMethodField()
    bank_fields = serializers.SerializerMethodField()
    nominee_fields = serializers.SerializerMethodField()
    documents = AgentDocumentSerializer(source="uploaded_documents", many=True, read_only=True)
    investor_count = serializers.SerializerMethodField()
    investment_count = serializers.SerializerMethodField()
    total_investment = serializers.SerializerMethodField()

    class Meta:
        model = Agent
        fields = (
            "id", "login_username", "login_password", "login_username_display", "login_password_status", "login_password_display",
            "user", "name", "email", "mobile", "image",
            "identity_details", "bank_details", "nominee_details",
            "identity_fields", "bank_fields", "nominee_fields", "documents", "other_details",
            "investor_count", "investment_count", "total_investment", "created_at", "updated_at",
        )
        read_only_fields = ("user", "created_at", "updated_at")

    def _section(self, obj, section):
        return list(obj.detail_fields.filter(section=section).values("id", "key", "value"))

    def get_login_username_display(self, obj):
        return obj.user.username if obj.user else "—"

    def get_login_password_status(self, obj):
        return "Password set" if obj.user and obj.user.has_usable_password() else "No password set"

    def get_login_password_display(self, obj):
        return obj.user.admin_visible_password if obj.user and obj.user.admin_visible_password else "Not available for this existing account"

    def get_identity_fields(self, obj):
        return self._section(obj, AgentDetailField.Sections.IDENTITY)

    def get_bank_fields(self, obj):
        return self._section(obj, AgentDetailField.Sections.BANK)

    def get_nominee_fields(self, obj):
        return self._section(obj, AgentDetailField.Sections.NOMINEE)

    def get_investor_count(self, obj):
        return Investor.objects.filter(agent=obj, deleted_at__isnull=True).count()

    def get_investment_count(self, obj):
        return Investment.objects.filter(
            agent_profit_share__agent=obj,
            deleted_at__isnull=True,
        ).count()

    def get_total_investment(self, obj):
        qs = Investment.objects.filter(
            agent_profit_share__agent=obj,
            deleted_at__isnull=True,
        ).select_related("scheme").prefetch_related("recurring_contributions")
        total = Decimal("0")
        for inv in qs:
            if inv.scheme.category == Scheme.Categories.PLATINUM:
                total += sum((Decimal(c.amount) for c in inv.recurring_contributions.filter(deleted_at__isnull=True)), Decimal("0"))
            else:
                total += Decimal(inv.amount or 0)
        return total

    def _parse_kv(self, value):
        if value in (None, ""):
            return []
        if isinstance(value, str):
            try:
                value = json.loads(value)
            except json.JSONDecodeError:
                raise serializers.ValidationError("Detail fields must be valid JSON.")
        if not isinstance(value, list):
            raise serializers.ValidationError("Detail fields must be a list.")
        cleaned = []
        for item in value:
            if not isinstance(item, dict) or not item.get("key"):
                raise serializers.ValidationError("Each detail field requires a key and value.")
            cleaned.append({"key": str(item["key"]), "value": str(item.get("value", ""))})
        return cleaned

    def create(self, validated_data):
        request = self.context["request"]
        username = validated_data.pop("login_username")
        password = validated_data.pop("login_password")
        identity = self._parse_kv(validated_data.pop("identity_details", []))
        bank = self._parse_kv(validated_data.pop("bank_details", []))
        nominee = self._parse_kv(validated_data.pop("nominee_details", []))

        if User.objects.filter(username__iexact=username).exists():
            raise serializers.ValidationError({"login_username": "This username is already registered."})
        email = validated_data.get("email") or ""
        if email and User.objects.filter(email__iexact=email).exists():
            raise serializers.ValidationError({"email": "This email is already registered."})

        user = User.objects.create_user(
            username=username,
            password=password,
            email=email or f"{username}@agent.local",
            role=User.Roles.AGENT,
            phone=validated_data.get("mobile", ""),
            admin_visible_password=password,
            is_active=True,
        )
        agent = Agent.objects.create(user=user, created_by=request.user, **validated_data)

        for section, rows in (
            (AgentDetailField.Sections.IDENTITY, identity),
            (AgentDetailField.Sections.BANK, bank),
            (AgentDetailField.Sections.NOMINEE, nominee),
        ):
            AgentDetailField.objects.bulk_create([
                AgentDetailField(agent=agent, section=section, key=x["key"], value=x["value"])
                for x in rows
            ])

        mapping = {
            "identity_files": AgentDocument.Sections.IDENTITY,
            "bank_files": AgentDocument.Sections.BANK,
            "nominee_files": AgentDocument.Sections.NOMINEE,
        }
        for input_name, section in mapping.items():
            titles = request.data.getlist(f"{input_name}_titles") if hasattr(request.data, "getlist") else []
            for index, file in enumerate(request.FILES.getlist(input_name)):
                title = titles[index] if index < len(titles) else file.name
                AgentDocument.objects.create(
                    agent=agent, section=section, title=title or file.name, file=file, uploaded_by=request.user
                )
        return agent


class AgentInvestmentShareSerializer(serializers.ModelSerializer):
    agent_name = serializers.CharField(source="agent.name", read_only=True)
    investor_name = serializers.CharField(source="investment.investor.name", read_only=True)
    business_name = serializers.CharField(source="investment.business.name", read_only=True)
    scheme_name = serializers.CharField(source="investment.scheme.name", read_only=True)

    class Meta:
        model = AgentInvestmentShare
        fields = (
            "id", "agent", "agent_name", "investment", "investor_name", "business_name", "scheme_name",
            "profit_percentage", "effective_from", "created_at", "updated_at",
        )
        read_only_fields = ("agent", "created_at", "updated_at")


class AgentLedgerEntrySerializer(serializers.ModelSerializer):
    investor_name = serializers.CharField(source="investor.name", read_only=True)
    business_name = serializers.CharField(source="business.name", read_only=True)
    scheme_name = serializers.SerializerMethodField()
    investment_id = serializers.IntegerField(source="investment.id", read_only=True)

    class Meta:
        model = AgentLedgerEntry
        fields = (
            "id", "agent", "investment", "investment_id", "investor", "investor_name", "business", "business_name",
            "scheme_name", "entry_date", "entry_type", "amount", "profit_percentage", "description", "reference", "created_at",
        )
        read_only_fields = ("created_at",)

    def get_scheme_name(self, obj):
        return obj.investment.scheme.name if obj.investment and obj.investment.scheme else "—"


class AgentPayoutSerializer(serializers.ModelSerializer):
    agent_name = serializers.CharField(source="agent.name", read_only=True)
    processed_by_name = serializers.CharField(source="processed_by.username", read_only=True, allow_null=True)

    class Meta:
        model = AgentPayout
        fields = (
            "id", "agent", "agent_name", "amount", "payout_date", "notes", "status",
            "requested_by", "processed_by", "processed_by_name", "processed_at", "created_at",
        )
        read_only_fields = ("requested_by", "processed_by", "processed_at", "created_at")


class AgentInvestmentSerializer(serializers.ModelSerializer):
    investor_name = serializers.CharField(source="investor.name", read_only=True)
    business_name = serializers.CharField(source="business.name", read_only=True)
    scheme_name = serializers.CharField(source="scheme.name", read_only=True)
    scheme_category = serializers.CharField(source="scheme.category", read_only=True)
    scheme_interest = serializers.DecimalField(source="scheme.annual_interest_rate", max_digits=7, decimal_places=4, read_only=True)
    withdrawal_type = serializers.CharField(source="scheme.withdrawal_type", read_only=True)
    total_contributed = serializers.SerializerMethodField()
    interest_earned_till_date = serializers.SerializerMethodField()
    maturity_amount = serializers.SerializerMethodField()
    agent_profit_percentage = serializers.SerializerMethodField()
    agent_profit_earned = serializers.SerializerMethodField()

    class Meta:
        model = Investment
        fields = (
            "id", "investor", "investor_name", "business", "business_name", "scheme", "scheme_name", "scheme_category",
            "scheme_interest", "withdrawal_type", "amount", "monthly_installment", "investor_annual_rate",
            "investment_date", "maturity_date", "status", "total_contributed", "interest_earned_till_date",
            "maturity_amount", "terms_snapshot", "agent_profit_percentage", "agent_profit_earned", "created_at",
        )
        read_only_fields = fields

    def get_total_contributed(self, obj):
        if obj.scheme.category == Scheme.Categories.PLATINUM:
            return sum((Decimal(c.amount) for c in obj.recurring_contributions.filter(deleted_at__isnull=True)), Decimal("0"))
        return obj.amount or Decimal("0")

    def get_interest_earned_till_date(self, obj):
        from admin_panel.services import diamond_interest_till_date, interest_till_date
        try:
            return diamond_interest_till_date(obj) if obj.scheme.category == Scheme.Categories.DIAMOND else interest_till_date(obj)
        except (ValueError, TypeError):
            return Decimal("0")

    def get_maturity_amount(self, obj):
        from admin_panel.services import maturity_value
        try:
            return maturity_value(obj)
        except (ValueError, TypeError):
            return None

    def get_agent_profit_percentage(self, obj):
        try:
            share = obj.agent_profit_share
        except AgentInvestmentShare.DoesNotExist:
            share = None
        return share.profit_percentage if share else None

    def get_agent_profit_earned(self, obj):
        return sum((Decimal(x.amount) for x in obj.agent_ledger_entries.filter(entry_type=AgentLedgerEntry.EntryTypes.SOFT_DEBIT)), Decimal("0"))


class AgentProfileSerializer(serializers.ModelSerializer):
    identity_details = serializers.SerializerMethodField()
    bank_details = serializers.SerializerMethodField()
    nominee_details = serializers.SerializerMethodField()
    documents = AgentDocumentSerializer(source="uploaded_documents", many=True, read_only=True)

    class Meta:
        model = Agent
        fields = ("id", "name", "email", "mobile", "image", "identity_details", "bank_details", "nominee_details", "documents", "other_details", "created_at")

    def _section(self, obj, section):
        return list(obj.detail_fields.filter(section=section).values("id", "key", "value"))

    def get_identity_details(self, obj): return self._section(obj, AgentDetailField.Sections.IDENTITY)
    def get_bank_details(self, obj): return self._section(obj, AgentDetailField.Sections.BANK)
    def get_nominee_details(self, obj): return self._section(obj, AgentDetailField.Sections.NOMINEE)
