from decimal import Decimal
from django.db import transaction
from django.db.models import Sum
from django.utils import timezone
from rest_framework import status
from rest_framework.parsers import JSONParser, MultiPartParser, FormParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from admin_panel.models import User, Investor, Investment, Business, Scheme
from admin_panel.serializers import InvestorSerializer, SchemeSerializer
from admin_panel.services import create_investment_with_snapshot, interest_till_date, diamond_interest_till_date
from admin_panel.views import AdminOnlyAPIView, BusinessPerformanceDashboardAPIView
from .models import Agent, AgentDetailField, AgentDocument, AgentInvestmentShare, AgentLedgerEntry, AgentPayout
from .serializers import (
    AgentSerializer, AgentProfileSerializer, AgentInvestmentSerializer,
    AgentInvestmentShareSerializer, AgentLedgerEntrySerializer, AgentPayoutSerializer,
)


class AgentOnlyAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def initial(self, request, *args, **kwargs):
        super().initial(request, *args, **kwargs)
        if request.user.role != User.Roles.AGENT:
            from rest_framework.exceptions import PermissionDenied
            raise PermissionDenied("You do not have permission to perform this action.")


def agent_for_user(user):
    return Agent.objects.filter(user=user, deleted_at__isnull=True).first()


def investor_queryset(agent):
    # Admin creates Investors; this relation controls whether an Investor
    # belongs to the logged-in Agent's portfolio.
    return Investor.objects.filter(
        agent=agent,
        deleted_at__isnull=True,
    ).order_by("name")


def investment_queryset(agent):
    # Agent visibility is based on the Admin-assigned AgentInvestmentShare,
    # never on Investment.created_by.
    return Investment.objects.filter(
        agent_profit_share__agent=agent,
        deleted_at__isnull=True,
    ).select_related("investor", "business", "scheme", "agent_profit_share").prefetch_related("recurring_contributions")


def ledger_totals(agent):
    soft = AgentLedgerEntry.objects.filter(agent=agent, entry_type=AgentLedgerEntry.EntryTypes.SOFT_DEBIT).aggregate(v=Sum("amount"))["v"] or Decimal("0")
    debit = AgentLedgerEntry.objects.filter(agent=agent, entry_type=AgentLedgerEntry.EntryTypes.DEBIT).aggregate(v=Sum("amount"))["v"] or Decimal("0")
    requested = AgentPayout.objects.filter(agent=agent, status=AgentPayout.Status.REQUESTED).aggregate(v=Sum("amount"))["v"] or Decimal("0")
    paid = AgentPayout.objects.filter(agent=agent, status=AgentPayout.Status.PAID).aggregate(v=Sum("amount"))["v"] or Decimal("0")
    return {
        "soft_debit": soft,
        "debit": debit,
        "requested_payout": requested,
        "paid_payout": paid,
        "available": max(Decimal("0"), soft - debit - requested),
    }


# ---------------------------------------------------------------------------
# ADMIN -> AGENT MANAGEMENT
# ---------------------------------------------------------------------------

class AgentListCreateAPIView(AdminOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def get(self, request):
        qs = Agent.objects.filter(deleted_at__isnull=True).select_related("user").prefetch_related("detail_fields", "uploaded_documents")
        return Response({"count": qs.count(), "results": AgentSerializer(qs, many=True, context={"request": request}).data})

    @transaction.atomic
    def post(self, request):
        serializer = AgentSerializer(data=request.data, context={"request": request})
        serializer.is_valid(raise_exception=True)
        obj = serializer.save()
        return Response(AgentSerializer(obj, context={"request": request}).data, status=status.HTTP_201_CREATED)


class AgentDetailAPIView(AdminOnlyAPIView):
    def get(self, request, pk):
        agent = Agent.objects.filter(pk=pk, deleted_at__isnull=True).select_related("user").prefetch_related("detail_fields", "uploaded_documents").first()
        if not agent:
            return Response({"detail": "Agent not found."}, status=404)

        investors = investor_queryset(agent)
        investments = investment_queryset(agent)
        totals = ledger_totals(agent)
        return Response({
            "agent": AgentSerializer(agent, context={"request": request}).data,
            "investors": InvestorSerializer(investors, many=True, context={"request": request}).data,
            "investments": AgentInvestmentSerializer(investments, many=True).data,
            "ledger_summary": totals,
        })
    
    
    def patch(self, request, pk):
        agent = Agent.objects.filter(
            pk=pk,
            deleted_at__isnull=True
        ).select_related("user").first()

        if not agent:
            return Response(
                {"detail": "Agent not found."},
                status=status.HTTP_404_NOT_FOUND
            )

        serializer = AgentSerializer(
            agent,
            data=request.data,
            partial=True,
            context={"request": request}
        )
        serializer.is_valid(raise_exception=True)
        agent = serializer.save()

        return Response(
            AgentSerializer(
                agent,
                context={"request": request}
            ).data,
            status=status.HTTP_200_OK
        )

    @transaction.atomic
    def delete(self, request, pk):
        agent = Agent.objects.filter(
            pk=pk,
            deleted_at__isnull=True
        ).select_related("user").first()

        if not agent:
            return Response(
                {"detail": "Agent not found."},
                status=status.HTTP_404_NOT_FOUND
            )

        # Soft-delete the Agent so historical records remain.
        agent.deleted_at = timezone.now()
        agent.save(update_fields=["deleted_at"])

        # Disable login without deleting the user account.
        if agent.user:
            agent.user.is_active = False
            agent.user.save(update_fields=["is_active"])

        return Response(
            {"detail": "Agent deleted successfully."},
            status=status.HTTP_200_OK
        )



class AdminAgentInvestmentLinksAPIView(AdminOnlyAPIView):
    """Small lookup used by the existing Admin Investments table for agent-created investments."""
    def get(self, request):
        qs = investment_queryset_for_admin()
        data = []
        for inv in qs:
            share = AgentInvestmentShare.objects.filter(
                investment=inv
            ).select_related("agent").first()
            if not share:
                continue
            agent = share.agent
            data.append({
                "investment": inv.id,
                "agent": agent.id,
                "agent_name": agent.name,
                "profit_percentage": share.profit_percentage if share else None,
                "effective_from": share.effective_from if share else None,
            })
        return Response({"count": len(data), "results": data})


def investment_queryset_for_admin():
    return Investment.objects.filter(deleted_at__isnull=True).select_related("created_by", "investor", "business", "scheme").order_by("-investment_date", "-id")


class AdminAgentInvestmentShareAPIView(AdminOnlyAPIView):
    @transaction.atomic
    def post(self, request, investment_id):
        investment = Investment.objects.filter(pk=investment_id, deleted_at__isnull=True).select_related("created_by", "investor", "business", "scheme").first()
        if not investment:
            return Response({"detail": "Investment not found."}, status=404)
        # The Agent is selected by Admin; Investment.created_by is always the
        # user who actually entered the investment and is not used for attribution.
        agent_id = request.data.get("agent")
        existing_share = AgentInvestmentShare.objects.filter(investment=investment).select_related("agent").first()
        agent = (
            Agent.objects.filter(pk=agent_id, deleted_at__isnull=True).first()
            if agent_id
            else (existing_share.agent if existing_share else None)
        )
        if not agent:
            return Response({"detail": "A valid Agent is required."}, status=400)
        try:
            percentage = Decimal(str(request.data.get("profit_percentage")))
        except Exception:
            return Response({"detail": "profit_percentage must be a valid number."}, status=400)
        if percentage < 0 or percentage > 100:
            return Response({"detail": "Profit percentage must be between 0 and 100."}, status=400)
        try:
            effective_from = request.data.get("effective_from") or timezone.localdate()
            if isinstance(effective_from, str):
                from datetime import date
                effective_from = date.fromisoformat(effective_from)
        except ValueError:
            return Response({"detail": "effective_from must be YYYY-MM-DD."}, status=400)

        share, _ = AgentInvestmentShare.objects.update_or_create(
            investment=investment,
            defaults={
                "agent": agent,
                "profit_percentage": percentage,
                "effective_from": effective_from,
                "created_by": request.user,
            },
        )
        if investment.investment_date <= timezone.localdate():
            try:
                from .services import generate_daily_agent_ledger
                generate_daily_agent_ledger(timezone.localdate())
            except Exception:
                pass
        return Response(AgentInvestmentShareSerializer(share).data)


class AdminAgentPayoutListCreateAPIView(AdminOnlyAPIView):
    def get(self, request, agent_id):
        agent = Agent.objects.filter(pk=agent_id, deleted_at__isnull=True).first()
        if not agent:
            return Response({"detail": "Agent not found."}, status=404)
        qs = AgentPayout.objects.filter(agent=agent).select_related("requested_by", "processed_by")
        return Response({"count": qs.count(), "results": AgentPayoutSerializer(qs, many=True).data, "ledger_summary": ledger_totals(agent)})

    @transaction.atomic
    def post(self, request, agent_id):
        agent = Agent.objects.filter(pk=agent_id, deleted_at__isnull=True).first()
        if not agent:
            return Response({"detail": "Agent not found."}, status=404)
        try:
            amount = Decimal(str(request.data.get("amount")))
        except Exception:
            return Response({"detail": "Enter a valid payout amount."}, status=400)
        available = ledger_totals(agent)["available"]
        if amount <= 0:
            return Response({"detail": "Payout amount must be greater than zero."}, status=400)
        if amount > available:
            return Response({"detail": f"Payout amount exceeds available profit. Available: {available}."}, status=400)
        payout = AgentPayout.objects.create(
            agent=agent, amount=amount,
            payout_date=request.data.get("payout_date") or timezone.localdate(),
            notes=request.data.get("notes") or "", status=AgentPayout.Status.PAID,
            requested_by=request.user, processed_by=request.user, processed_at=timezone.now(),
        )
        AgentLedgerEntry.objects.create(
            agent=agent, entry_date=payout.payout_date,
            entry_type=AgentLedgerEntry.EntryTypes.DEBIT, amount=amount,
            description="Agent profit payout processed by Admin.",
            reference=f"AGENT-PAYOUT-{payout.pk}",
        )
        return Response(AgentPayoutSerializer(payout).data, status=201)


class AdminAgentPayoutProcessAPIView(AdminOnlyAPIView):
    @transaction.atomic
    def post(self, request, agent_id, payout_id):
        agent = Agent.objects.filter(pk=agent_id, deleted_at__isnull=True).first()
        payout = AgentPayout.objects.filter(pk=payout_id, agent=agent).first() if agent else None
        if not payout:
            return Response({"detail": "Agent payout not found."}, status=404)
        if payout.status != AgentPayout.Status.REQUESTED:
            return Response({"detail": "Only requested payouts can be processed."}, status=400)
        available = ledger_totals(agent)["available"]
        if Decimal(payout.amount) > available:
            return Response({"detail": f"Insufficient available Agent profit. Available: {available}."}, status=400)

        payout.status = AgentPayout.Status.PAID
        payout.processed_by = request.user
        payout.processed_at = timezone.now()
        payout.save(update_fields=["status", "processed_by", "processed_at"])
        AgentLedgerEntry.objects.create(
            agent=agent,
            entry_date=payout.payout_date,
            entry_type=AgentLedgerEntry.EntryTypes.DEBIT,
            amount=payout.amount,
            description="Agent profit payout processed by Admin.",
            reference=f"AGENT-PAYOUT-{payout.pk}",
        )
        return Response(AgentPayoutSerializer(payout).data)


class AdminAgentPayoutRejectAPIView(AdminOnlyAPIView):
    @transaction.atomic
    def post(self, request, agent_id, payout_id):
        agent = Agent.objects.filter(pk=agent_id, deleted_at__isnull=True).first()
        payout = AgentPayout.objects.filter(pk=payout_id, agent=agent).first() if agent else None
        if not payout:
            return Response({"detail": "Agent payout not found."}, status=404)
        if payout.status != AgentPayout.Status.REQUESTED:
            return Response({"detail": "Only requested payouts can be rejected."}, status=400)
        payout.status = AgentPayout.Status.REJECTED
        payout.processed_by = request.user
        payout.processed_at = timezone.now()
        payout.save(update_fields=["status", "processed_by", "processed_at"])
        return Response(AgentPayoutSerializer(payout).data)


# ---------------------------------------------------------------------------
# AGENT PORTAL
# ---------------------------------------------------------------------------

class AgentPortalProfileAPIView(AgentOnlyAPIView):
    def get(self, request):
        agent = agent_for_user(request.user)
        if not agent:
            return Response({"detail": "Agent account is not linked to an Agent record."}, status=404)
        return Response(AgentProfileSerializer(agent).data)


class AgentPortalDashboardAPIView(AgentOnlyAPIView):
    def get(self, request):
        agent = agent_for_user(request.user)
        if not agent:
            return Response({"detail": "Agent account is not linked to an Agent record."}, status=404)
        investors = investor_queryset(agent)
        investments = investment_queryset(agent)
        totals = ledger_totals(agent)

        total_investment = Decimal("0")
        total_interest = Decimal("0")
        for inv in investments:
            if inv.scheme.category == Scheme.Categories.PLATINUM:
                total_investment += sum((Decimal(c.amount) for c in inv.recurring_contributions.filter(deleted_at__isnull=True)), Decimal("0"))
            else:
                total_investment += Decimal(inv.amount or 0)
            try:
                total_interest += Decimal(diamond_interest_till_date(inv) if inv.scheme.category == Scheme.Categories.DIAMOND else interest_till_date(inv))
            except (ValueError, TypeError):
                pass

        business_ids = investments.values_list("business_id", flat=True).distinct()
        scheme_map = {}
        business_map = {}
        for inv in investments:
            contributed = Decimal(inv.amount or 0) if inv.scheme.category != Scheme.Categories.PLATINUM else sum((Decimal(c.amount) for c in inv.recurring_contributions.filter(deleted_at__isnull=True)), Decimal("0"))
            b = business_map.setdefault(inv.business_id, {"business_id": inv.business_id, "business_name": inv.business.name, "investment": Decimal("0")})
            b["investment"] += contributed
            key = inv.scheme_id
            s = scheme_map.setdefault(key, {"scheme_id": inv.scheme_id, "scheme_name": inv.scheme.name, "scheme_category": inv.scheme.get_category_display(), "investment": Decimal("0")})
            s["investment"] += contributed

        return Response({
            "kpis": {
                "total_investors": investors.count(),
                "total_investments": investments.count(),
                "total_investment": total_investment,
                "interest_earned_till_date": total_interest,
                "business_count": len(business_ids),
                "agent_profit_earned": totals["soft_debit"],
                "agent_profit_paid": totals["debit"],
                "agent_profit_available": totals["available"],
            },
            "charts": {
                "business_wise": list(business_map.values()),
                "scheme_wise": list(scheme_map.values()),
            },
        })


class AgentPortalInvestorListCreateAPIView(AgentOnlyAPIView):
    parser_classes = [MultiPartParser, FormParser, JSONParser]

    def get(self, request):
        agent = agent_for_user(request.user)
        if not agent:
            return Response({"detail": "Agent account is not linked to an Agent record."}, status=404)
        qs = investor_queryset(agent).prefetch_related("detail_fields", "uploaded_documents")
        return Response({"count": qs.count(), "results": InvestorSerializer(qs, many=True, context={"request": request}).data})

    def post(self, request):
        return Response(
            {"detail": "Agents cannot add Investors. Investors are created by Admin and assigned to an Agent."},
            status=405,
        )


class AgentPortalInvestorDetailAPIView(AgentOnlyAPIView):
    def get(self, request, pk):
        agent = agent_for_user(request.user)
        investor = Investor.objects.filter(pk=pk, agent=agent, deleted_at__isnull=True).first() if agent else None
        if not investor:
            return Response({"detail": "Investor not found."}, status=404)
        investments = investment_queryset(agent).filter(investor=investor)
        return Response({
            "investor": InvestorSerializer(investor, context={"request": request}).data,
            "investments": AgentInvestmentSerializer(investments, many=True).data,
        })


class AgentPortalInvestmentListCreateAPIView(AgentOnlyAPIView):
    def get(self, request):
        agent = agent_for_user(request.user)
        if not agent:
            return Response({"detail": "Agent account is not linked to an Agent record."}, status=404)
        qs = investment_queryset(agent)
        return Response({"count": qs.count(), "results": AgentInvestmentSerializer(qs, many=True).data})

    def post(self, request):
        return Response(
            {"detail": "Agents cannot add Investments. Investments are created by Admin and assigned to an Agent."},
            status=405,
        )



class AgentPortalInvestmentDetailAPIView(AgentOnlyAPIView):
    def get(self, request, pk):
        agent = agent_for_user(request.user)
        inv = investment_queryset(agent).filter(pk=pk).first() if agent else None
        if not inv:
            return Response({"detail": "Investment not found."}, status=404)
        return Response({
            "investment": AgentInvestmentSerializer(inv).data,
            "investor": InvestorSerializer(inv.investor, context={"request": request}).data,
        })


class AgentPortalAvailableBusinessesAPIView(AgentOnlyAPIView):
    def get(self, request):
        from admin_panel.serializers import BusinessSerializer
        businesses = Business.objects.filter(deleted_at__isnull=True).order_by("name")
        return Response({"count": businesses.count(), "results": BusinessSerializer(businesses, many=True, context={"request": request}).data})


class AgentPortalAvailableSchemesAPIView(AgentOnlyAPIView):
    def get(self, request):
        schemes = Scheme.objects.filter(deleted_at__isnull=True, is_active=True).order_by("category", "tenure_months", "name")
        return Response({"count": schemes.count(), "results": SchemeSerializer(schemes, many=True).data})


class AgentPortalBusinessPerformanceAPIView(AgentOnlyAPIView):
    def get(self, request):
        # Reuse the existing canonical Business Health API so the Agent sees
        # the same KPI + P&L chart structure already used by Admin/Investor.
        return BusinessPerformanceDashboardAPIView().get(request)


class AgentPortalLedgerAPIView(AgentOnlyAPIView):
    def get(self, request):
        agent = agent_for_user(request.user)
        if not agent:
            return Response({"detail": "Agent account is not linked to an Agent record."}, status=404)
        qs = AgentLedgerEntry.objects.filter(agent=agent).select_related("investment", "investor", "business", "investment__scheme")
        if request.query_params.get("date"):
            qs = qs.filter(entry_date=request.query_params["date"])
        if request.query_params.get("entry_type"):
            qs = qs.filter(entry_type=request.query_params["entry_type"])
        return Response({"count": qs.count(), "results": AgentLedgerEntrySerializer(qs, many=True).data, "ledger_summary": ledger_totals(agent)})


class AgentPortalPayoutListCreateAPIView(AgentOnlyAPIView):
    def get(self, request):
        agent = agent_for_user(request.user)
        if not agent:
            return Response({"detail": "Agent account is not linked to an Agent record."}, status=404)
        qs = AgentPayout.objects.filter(agent=agent).select_related("processed_by")
        return Response({"count": qs.count(), "results": AgentPayoutSerializer(qs, many=True).data, "ledger_summary": ledger_totals(agent)})

    @transaction.atomic
    def post(self, request):
        agent = agent_for_user(request.user)
        if not agent:
            return Response({"detail": "Agent account is not linked to an Agent record."}, status=404)
        try:
            amount = Decimal(str(request.data.get("amount")))
        except Exception:
            return Response({"detail": "Enter a valid payout amount."}, status=400)
        available = ledger_totals(agent)["available"]
        if amount <= 0:
            return Response({"detail": "Payout amount must be greater than zero."}, status=400)
        if amount > available:
            return Response({"detail": f"Payout amount exceeds available profit. Available: {available}."}, status=400)
        payout = AgentPayout.objects.create(
            agent=agent,
            amount=amount,
            payout_date=request.data.get("payout_date") or timezone.localdate(),
            notes=request.data.get("notes") or "",
            status=AgentPayout.Status.REQUESTED,
            requested_by=request.user,
        )
        return Response(AgentPayoutSerializer(payout).data, status=201)
