import json
from django.contrib.auth.password_validation import validate_password
from rest_framework import serializers
from .models import (
    User, Business, BusinessBankDetail, BusinessDetailField, BusinessDocument,
    Investor, InvestorDetailField, InvestorDocument, Scheme, KuberBusinessInvestment,
    Investment, BalanceSheet, LedgerEntry, Payout, Certificate, CertificateAttachment,
    AuditLog, RecurringContribution, AccountantPermission
)
from decimal import Decimal
from .services import maturity_value, premature_value


class UserCreateSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, min_length=8)

    class Meta:
        model = User
        fields = ("id", "username", "email", "password", "first_name", "last_name", "phone", "role")

    def validate_password(self, value):
        validate_password(value)
        return value

    def create(self, validated_data):
        password = validated_data.pop("password")
        user = User(**validated_data)
        user.set_password(password)
        user.save()
        return user


class UserOutputSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ("id", "username", "email", "first_name", "last_name", "phone", "role", "is_active", "is_staff", "is_superuser")


class KeyValueRelationField(serializers.Field):
    def __init__(self, relation_name, section=None, **kwargs):
        self.relation_name = relation_name
        self.section = section
        super().__init__(**kwargs)

    def get_attribute(self, instance):
        return instance

    def to_representation(self, instance):
        return [
            {"id": item.pk, "key": item.key, "value": item.value}
            for item in getattr(instance, self.relation_name).all()
            if self.section is None or getattr(item, "section", None) == self.section
        ]

    def to_internal_value(self, data):
        if isinstance(data, str):
            try:
                data = json.loads(data)
            except json.JSONDecodeError:
                raise serializers.ValidationError("Expected a JSON array of {key, value} objects.")
        if not isinstance(data, list):
            raise serializers.ValidationError("Expected a list of {key, value} objects.")
        result = []
        for item in data:
            if not isinstance(item, dict) or not str(item.get("key", "")).strip():
                raise serializers.ValidationError("Each item must contain a non-empty key and a value.")
            result.append({"key": str(item["key"]).strip(), "value": str(item.get("value", ""))})
        return result


class BusinessDocumentSerializer(serializers.ModelSerializer):
    class Meta:
        model = BusinessDocument
        fields = ("id", "business", "title", "file", "uploaded_by", "created_at")
        read_only_fields = ("uploaded_by", "created_at")


class BusinessSerializer(serializers.ModelSerializer):
    login_username = serializers.CharField(write_only=True, required=False)
    login_password = serializers.CharField(write_only=True, required=False, min_length=8)
    bank_details = KeyValueRelationField("bank_detail_fields", required=False)
    business_details = KeyValueRelationField("detail_fields", required=False)
    documents = BusinessDocumentSerializer(source="uploaded_documents", many=True, read_only=True)
    investor_count = serializers.IntegerField(read_only=True)
    total_investment = serializers.SerializerMethodField()

    class Meta:
        model = Business
        fields = (
            "id", "login_username", "login_password", "name", "location", "owner_name",
            "owner_aadhar", "owner_pan", "owner_mobile", "owner_sex",
            "bank_details", "business_details", "owner_photo", "business_photo",
            "documents", "investor_count", "total_investment", "created_at", "updated_at"
        )
        read_only_fields = ("created_by", "created_at", "updated_at")

    def get_total_investment(self, obj):
        from django.db.models import Sum
        one_time = obj.investments.filter(deleted_at__isnull=True, amount__isnull=False).aggregate(v=Sum("amount"))["v"] or Decimal("0")
        recurring = obj.investments.filter(deleted_at__isnull=True, scheme__category=Scheme.Categories.PLATINUM).aggregate(v=Sum("recurring_contributions__amount"))["v"] or Decimal("0")
        return one_time + recurring

    def _replace_kv(self, business, bank_details=None, business_details=None):
        if bank_details is not None:
            BusinessBankDetail.objects.filter(business=business).delete()
            BusinessBankDetail.objects.bulk_create([
                BusinessBankDetail(business=business, key=x["key"], value=x["value"]) for x in bank_details
            ])
        if business_details is not None:
            BusinessDetailField.objects.filter(business=business).delete()
            BusinessDetailField.objects.bulk_create([
                BusinessDetailField(business=business, key=x["key"], value=x["value"]) for x in business_details
            ])

    def create(self, validated_data):
        username = validated_data.pop("login_username", None)
        password = validated_data.pop("login_password", None)
        bank_details = validated_data.pop("bank_details", [])
        business_details = validated_data.pop("business_details", [])
        request = self.context["request"]
        user = None
        if username and password:
            user = User.objects.create_user(
                username=username, password=password,
                email=f"{username}@business.local", role=User.Roles.BUSINESS,
                phone=validated_data.get("owner_mobile", ""),
            )
        validated_data["created_by"] = request.user
        validated_data["user"] = user
        business = super().create(validated_data)
        self._replace_kv(business, bank_details, business_details)
        self._save_uploaded_documents(business, request)
        return business

    def update(self, instance, validated_data):
        validated_data.pop("login_username", None)
        validated_data.pop("login_password", None)
        bank_details = validated_data.pop("bank_details", None)
        business_details = validated_data.pop("business_details", None)
        request = self.context.get("request")
        instance = super().update(instance, validated_data)
        self._replace_kv(instance, bank_details, business_details)
        if request:
            self._save_uploaded_documents(instance, request)
        return instance

    def _save_uploaded_documents(self, business, request):
        files = request.FILES.getlist("documents") if request else []
        titles = request.data.getlist("document_titles") if hasattr(request.data, "getlist") else []
        for index, file in enumerate(files):
            title = titles[index] if index < len(titles) else file.name
            BusinessDocument.objects.create(
                business=business, title=title or file.name, file=file, uploaded_by=request.user
            )


class InvestorDocumentSerializer(serializers.ModelSerializer):
    class Meta:
        model = InvestorDocument
        fields = ("id", "section", "title", "file", "uploaded_by", "created_at")
        read_only_fields = ("uploaded_by", "created_at")


class InvestorDetailFieldSerializer(serializers.ModelSerializer):
    class Meta:
        model = InvestorDetailField
        fields = ("id", "section", "key", "value")


class InvestorSerializer(serializers.ModelSerializer):
    login_username = serializers.CharField(write_only=True, required=False)
    login_password = serializers.CharField(write_only=True, required=False, min_length=8)
    identity_details = KeyValueRelationField("detail_fields", section="IDENTITY", required=False)
    bank_details = KeyValueRelationField("detail_fields", section="BANK", required=False)
    nominee_details = KeyValueRelationField("detail_fields", section="NOMINEE", required=False)
    identity_fields = serializers.SerializerMethodField()
    bank_fields = serializers.SerializerMethodField()
    nominee_fields = serializers.SerializerMethodField()
    documents = InvestorDocumentSerializer(source="uploaded_documents", many=True, read_only=True)
    total_investment = serializers.SerializerMethodField()
    business_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Investor
        fields = (
            "id", "login_username", "login_password", "user", "name", "email", "mobile",
            "identity_details", "bank_details", "nominee_details",
            "identity_fields", "bank_fields", "nominee_fields", "documents", "other_details",
            "total_investment", "business_count", "created_at", "updated_at"
        )
        read_only_fields = ("created_by", "created_at", "updated_at")

    def get_total_investment(self, obj):
        from django.db.models import Sum
        one_time = obj.investments.filter(deleted_at__isnull=True, amount__isnull=False).aggregate(v=Sum("amount"))["v"] or Decimal("0")
        recurring = obj.investments.filter(deleted_at__isnull=True, scheme__category=Scheme.Categories.PLATINUM).aggregate(v=Sum("recurring_contributions__amount"))["v"] or Decimal("0")
        return one_time + recurring

    def _section(self, investor, section):
        return list(investor.detail_fields.filter(section=section).values("id", "key", "value"))

    def get_identity_fields(self, obj):
        return self._section(obj, InvestorDetailField.Sections.IDENTITY)

    def get_bank_fields(self, obj):
        return self._section(obj, InvestorDetailField.Sections.BANK)

    def get_nominee_fields(self, obj):
        return self._section(obj, InvestorDetailField.Sections.NOMINEE)

    def _replace_section(self, investor, section, values):
        InvestorDetailField.objects.filter(investor=investor, section=section).delete()
        InvestorDetailField.objects.bulk_create([
            InvestorDetailField(investor=investor, section=section, key=x["key"], value=x["value"])
            for x in values
        ])

    def _save_files(self, investor, request):
        if not request:
            return
        mapping = {
            "identity_files": InvestorDocument.Sections.IDENTITY,
            "bank_files": InvestorDocument.Sections.BANK,
            "nominee_files": InvestorDocument.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
                InvestorDocument.objects.create(
                    investor=investor, section=section, title=title or file.name,
                    file=file, uploaded_by=request.user
                )

    def create(self, validated_data):
        username = validated_data.pop("login_username", None)
        password = validated_data.pop("login_password", None)
        identity = validated_data.pop("identity_details", [])
        bank = validated_data.pop("bank_details", [])
        nominee = validated_data.pop("nominee_details", [])
        request = self.context["request"]
        user = None
        if username and password:
            user = User.objects.create_user(
                username=username, password=password,
                email=validated_data.get("email") or f"{username}@investor.local",
                role=User.Roles.INVESTOR, phone=validated_data.get("mobile", ""),
            )
        validated_data["created_by"] = request.user
        validated_data["user"] = user
        investor = super().create(validated_data)
        self._replace_section(investor, InvestorDetailField.Sections.IDENTITY, identity)
        self._replace_section(investor, InvestorDetailField.Sections.BANK, bank)
        self._replace_section(investor, InvestorDetailField.Sections.NOMINEE, nominee)
        self._save_files(investor, request)
        return investor

    def update(self, instance, validated_data):
        validated_data.pop("login_username", None)
        validated_data.pop("login_password", None)
        identity = validated_data.pop("identity_details", None)
        bank = validated_data.pop("bank_details", None)
        nominee = validated_data.pop("nominee_details", None)
        request = self.context.get("request")
        instance = super().update(instance, validated_data)
        if identity is not None:
            self._replace_section(instance, InvestorDetailField.Sections.IDENTITY, identity)
        if bank is not None:
            self._replace_section(instance, InvestorDetailField.Sections.BANK, bank)
        if nominee is not None:
            self._replace_section(instance, InvestorDetailField.Sections.NOMINEE, nominee)
        self._save_files(instance, request)
        return instance


class SchemeSerializer(serializers.ModelSerializer):
    scheme_display_name = serializers.SerializerMethodField()

    class Meta:
        model = Scheme
        fields = (
            "id", "category", "name", "description", "tenure_months",
            "annual_interest_rate", "penalty_rate", "withdrawal_type",
            "compounding_frequency", "rules", "is_active", "scheme_display_name",
            "created_at", "updated_at"
        )
        read_only_fields = ("created_by", "created_at", "updated_at", "scheme_display_name")

    def get_scheme_display_name(self, obj):
        return f"{obj.get_category_display()}-{obj.name}-{obj.tenure_months} months-{obj.annual_interest_rate}% p.a."

    def validate(self, attrs):
        category = attrs.get("category", getattr(self.instance, "category", None))
        if category == Scheme.Categories.SILVER and not attrs.get(
            "withdrawal_type", getattr(self.instance, "withdrawal_type", "")
        ):
            raise serializers.ValidationError({"withdrawal_type": "Required for Silver."})
        if category == Scheme.Categories.GOLD and not attrs.get(
            "compounding_frequency", getattr(self.instance, "compounding_frequency", "")
        ):
            attrs["compounding_frequency"] = "MONTHLY"
        if category == Scheme.Categories.PLATINUM:
            # Platinum has no amount field on Scheme.
            pass
        return attrs


class KuberBusinessInvestmentSerializer(serializers.ModelSerializer):
    business_name = serializers.CharField(source="business.name", read_only=True)

    class Meta:
        model = KuberBusinessInvestment
        fields = (
            "id", "business", "business_name", "amount", "annual_rate",
            "investment_date", "maturity_date", "notes", "created_at", "updated_at"
        )
        read_only_fields = ("created_by", "created_at", "updated_at")


class InvestmentSerializer(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_display_name = serializers.SerializerMethodField()
    maturity_amount = serializers.SerializerMethodField()
    premature_amount = serializers.SerializerMethodField()
    total_contributed = serializers.SerializerMethodField()

    class Meta:
        model = Investment
        fields = (
            "id", "investor", "investor_name", "business", "business_name",
            "scheme", "scheme_name", "scheme_display_name",
            "amount", "monthly_installment", "investor_annual_rate",
            "investment_date", "maturity_date", "status", "terms_snapshot",
            "total_contributed", "maturity_amount", "premature_amount",
            "created_at", "updated_at"
        )
        read_only_fields = (
            "created_by", "maturity_date", "terms_snapshot", "investor_annual_rate",
            "created_at", "updated_at", "maturity_amount", "premature_amount",
            "total_contributed", "scheme_display_name"
        )

    def get_scheme_display_name(self, obj):
        return f"{obj.scheme.get_category_display()}-{obj.scheme.name}-{obj.scheme.tenure_months} months-{obj.scheme.annual_interest_rate}% p.a."

    def get_total_contributed(self, obj):
        from django.db.models import Sum
        if obj.scheme.category == Scheme.Categories.PLATINUM:
            return obj.recurring_contributions.aggregate(v=Sum("amount"))["v"] or Decimal("0")
        return obj.amount or Decimal("0")

    def get_maturity_amount(self, obj):
        try:
            return maturity_value(obj)
        except ValueError:
            return None

    def get_premature_amount(self, obj):
        try:
            return premature_value(obj)
        except ValueError:
            return None

    def validate(self, attrs):
        scheme = attrs.get("scheme", getattr(self.instance, "scheme", None))
        amount = attrs.get("amount", getattr(self.instance, "amount", None))
        monthly = attrs.get("monthly_installment", getattr(self.instance, "monthly_installment", None))
        if scheme:
            if scheme.category == Scheme.Categories.PLATINUM:
                if monthly is None:
                    raise serializers.ValidationError({"monthly_installment": "Required for Platinum investment."})
                attrs["amount"] = None
            else:
                if amount is None:
                    raise serializers.ValidationError({"amount": "Required for Silver, Gold and Diamond investment."})
                attrs["monthly_installment"] = None
        return attrs

    def create(self, validated_data):
        from .services import create_investment_with_snapshot
        request = self.context["request"]
        scheme = validated_data["scheme"]
        return create_investment_with_snapshot(
            investor=validated_data["investor"],
            business=validated_data["business"],
            scheme=scheme,
            amount=validated_data.get("amount"),
            monthly_installment=validated_data.get("monthly_installment"),
            created_by=request.user,
            investment_date=validated_data.get("investment_date"),
        )

    def update(self, instance, validated_data):
        from .services import months_after, build_terms_snapshot
        scheme = validated_data.get("scheme", instance.scheme)
        amount = validated_data.get("amount", instance.amount)
        monthly = validated_data.get("monthly_installment", instance.monthly_installment)
        if scheme.category == Scheme.Categories.PLATINUM:
            amount = None
        else:
            monthly = None
        instance = super().update(instance, {**validated_data, "amount": amount, "monthly_installment": monthly,
                                             "investor_annual_rate": scheme.annual_interest_rate})
        instance.maturity_date = months_after(instance.investment_date, scheme.tenure_months)
        instance.terms_snapshot = build_terms_snapshot(scheme, instance)
        instance.save(update_fields=["maturity_date", "terms_snapshot", "updated_at", "investor_annual_rate"])
        return instance


class BalanceSheetSerializer(serializers.ModelSerializer):
    business_name = serializers.CharField(source="business.name", read_only=True)
    class Meta:
        model = BalanceSheet
        fields = ("id", "business", "business_name", "statement_date", "file", "uploaded_by", "created_at", "updated_at")
        read_only_fields = ("uploaded_by", "created_at", "updated_at")


class LedgerEntrySerializer(serializers.ModelSerializer):
    investor_name = serializers.CharField(source="investor.name", read_only=True)
    business_name = serializers.CharField(source="business.name", read_only=True)
    class Meta:
        model = LedgerEntry
        fields = ("id", "business", "business_name", "investment", "investor", "investor_name",
                  "kuber_investment", "entry_date", "entry_type", "amount", "annual_rate",
                  "description", "reference", "created_at")
        read_only_fields = ("created_at",)


class PayoutSerializer(serializers.ModelSerializer):
    investor_name = serializers.CharField(source="investor.name", read_only=True)
    class Meta:
        model = Payout
        fields = ("id", "investment", "investor", "investor_name", "amount", "payout_date", "notes", "created_by", "created_at")
        read_only_fields = ("investor", "created_by", "created_at")
    def validate(self, attrs):
        investment = attrs["investment"]
        if investment.status in (Investment.Status.CANCELLED, Investment.Status.CLOSED):
            raise serializers.ValidationError("Cannot create payout for a closed/cancelled investment.")
        attrs["investor"] = investment.investor
        return attrs


class RecurringContributionSerializer(serializers.ModelSerializer):
    class Meta:
        model = RecurringContribution
        fields = ("id", "investment", "contribution_date", "amount", "created_by", "created_at")
        read_only_fields = ("created_by", "created_at")

    def validate(self, attrs):
        investment = self.context.get("investment")
        if investment and investment.scheme.category != Scheme.Categories.PLATINUM:
            raise serializers.ValidationError("Recurring contributions are only for Platinum investments.")
        return attrs


class CertificateAttachmentSerializer(serializers.ModelSerializer):
    class Meta:
        model = CertificateAttachment
        fields = ("id", "title", "file", "sort_order", "created_at")
        read_only_fields = ("created_at",)


class CertificateSerializer(serializers.ModelSerializer):
    attachments = CertificateAttachmentSerializer(many=True, read_only=True)

    class Meta:
        model = Certificate
        fields = ("id", "investment", "html_content", "version", "attachments", "created_by", "created_at", "updated_at")
        read_only_fields = ("created_by", "created_at", "updated_at", "version")


class AuditLogSerializer(serializers.ModelSerializer):
    actor_name = serializers.CharField(source="actor.username", read_only=True)
    class Meta:
        model = AuditLog
        fields = ("id", "actor", "actor_name", "operation", "module", "object_type",
                  "object_id", "description", "metadata", "created_at")


class AccountantPermissionSerializer(serializers.ModelSerializer):
    class Meta:
        model = AccountantPermission
        fields = ("user", "permissions", "updated_at")
        read_only_fields = ("updated_at",)
