from rest_framework import serializers
from .models import (
    Project, ProjectDocument, ProjectTeamMember,
    ProjectFinance, ProjectDissemination, ProjectSchedule, ProjectFund,
    ProjectFundAllocation, ProjectSettings,
    FundingOpportunity, FundingProposal, FundReport,
    ProjectFundTranche, ProjectFundCompliance, FundDocument,
    ProjectResource, ProjectLog,
)


class ProjectSettingsSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProjectSettings
        fields = [
            "access_level", "donor_pic_can_access",
            "proposal_template", "report_template", "updated_at",
        ]
        read_only_fields = ["updated_at"]


class ProjectSerializer(serializers.ModelSerializer):
    """Serializer for Project model."""
    lead_name = serializers.SerializerMethodField()
    fund_count = serializers.SerializerMethodField()
    fund_totals = serializers.SerializerMethodField()
    fund_donors = serializers.SerializerMethodField()

    class Meta:
        model = Project
        fields = [
            "id", "name", "description", "status", "lead", "lead_name",
            "start_date", "end_date", "tags",
            "fund_count", "fund_totals", "fund_donors",
            "created_at", "updated_at",
        ]
        read_only_fields = ["id", "created_at", "updated_at", "fund_count", "fund_totals", "fund_donors"]

    def get_lead_name(self, obj):
        if not obj.lead_id:
            return None
        user = getattr(obj.lead, "user", None)
        if user is None:
            return None
        return user.get_full_name() or user.username or user.email

    def get_fund_count(self, obj) -> int:
        # funds is prefetched on the ViewSet; len() reads the cache instead of
        # issuing a COUNT query per row.
        return len(obj.funds.all())

    def get_fund_totals(self, obj) -> dict:
        totals: dict[str, float] = {}
        for f in obj.funds.all():
            totals[f.currency] = float(totals.get(f.currency, 0)) + float(f.amount)
        return totals

    # Funding is "secured" once it reaches pledged or later. Prospect/proposal
    # funds are not yet committed money.
    _SECURED_STATUSES = {"pledged", "received", "allocated", "closed"}

    def _fund_donor_name(self, f):
        donor = getattr(f, "donor", None)
        # Prefer the canonical donor record; fall back to the free-text source.
        return (getattr(donor, "name", None) if donor else None) or (f.source or None)

    def get_fund_donors(self, obj) -> list:
        # Once any fund is secured, the project's donors come from the secured
        # funds only. Before that, show the prospect/proposal donors so the
        # pipeline is still visible.
        funds = list(obj.funds.all())
        secured = [f for f in funds if f.status in self._SECURED_STATUSES]
        relevant = secured if secured else funds

        names: list[str] = []
        for f in relevant:
            name = self._fund_donor_name(f)
            if name and name not in names:
                names.append(name)
        return names


class ProjectDocumentSerializer(serializers.ModelSerializer):
    """Serializer for ProjectDocument model."""
    project_name = serializers.SerializerMethodField()
    uploaded_by_name = serializers.SerializerMethodField()
    uploaded_by_email = serializers.SerializerMethodField()
    file_url = serializers.SerializerMethodField()
    file_size = serializers.SerializerMethodField()
    file_ext = serializers.SerializerMethodField()
    allowed_user_names = serializers.SerializerMethodField()

    class Meta:
        model = ProjectDocument
        fields = [
            "id", "project", "project_name",
            "name", "file", "file_url", "file_size", "file_ext",
            "uploaded_by", "uploaded_by_name", "uploaded_by_email",
            "access_level", "allowed_users", "allowed_user_names",
            "created_at",
        ]
        read_only_fields = [
            "id", "created_at", "file_url", "file_size", "file_ext",
            "project_name",
            "uploaded_by_name", "uploaded_by_email", "allowed_user_names",
        ]

    def validate_file(self, value):
        from apps.core.uploads import validate_upload
        if value:
            validate_upload(value)
        return value

    def get_project_name(self, obj):
        return getattr(obj.project, "name", None) if obj.project_id else None

    def get_uploaded_by_email(self, obj):
        return getattr(obj.uploaded_by, "email", None) if obj.uploaded_by_id else None

    def get_uploaded_by_name(self, obj):
        if obj.uploaded_by:
            return obj.uploaded_by.get_full_name() or obj.uploaded_by.username
        return None

    def get_file_url(self, obj):
        if not obj.file:
            return None
        request = self.context.get("request")
        url = obj.file.url
        return request.build_absolute_uri(url) if request else url

    def get_file_size(self, obj):
        if not obj.file:
            return None
        try:
            return obj.file.size
        except (FileNotFoundError, ValueError, OSError):
            return None

    def get_file_ext(self, obj):
        if not obj.file:
            return None
        name = obj.file.name or ""
        _, _, ext = name.rpartition(".")
        return ext.lower() if ext and ext != name else None

    def get_allowed_user_names(self, obj):
        return [
            (u.get_full_name() or u.username or u.email)
            for u in obj.allowed_users.all()
        ]


class ProjectTeamMemberSerializer(serializers.ModelSerializer):
    """Serializer for ProjectTeamMember model."""
    user_name = serializers.SerializerMethodField()
    project_name = serializers.SerializerMethodField()

    class Meta:
        model = ProjectTeamMember
        fields = ["id", "project", "project_name", "user", "user_name", "role", "joined_at"]
        read_only_fields = ["id", "joined_at"]

    def get_user_name(self, obj):
        return obj.user.get_full_name() or obj.user.username

    def get_project_name(self, obj):
        return obj.project.name


class ProjectFinanceSerializer(serializers.ModelSerializer):
    project_id = serializers.CharField(source="project.id", read_only=True)
    ledger_label = serializers.CharField(source="get_ledger_display", read_only=True)
    budget_item_code = serializers.CharField(source="budget_item.code", read_only=True, default=None)
    budget_item_name = serializers.CharField(source="budget_item.name", read_only=True, default=None)
    budget_item_category = serializers.CharField(source="budget_item.category.name", read_only=True, default=None)
    budget_category_code = serializers.CharField(source="budget_category.code", read_only=True, default=None)
    budget_category_name = serializers.CharField(source="budget_category.name", read_only=True, default=None)

    class Meta:
        model = ProjectFinance
        fields = [
            "id", "project", "project_id", "description", "amount", "currency", "type", "ledger", "ledger_label",
            "category", "budget_category", "budget_category_code", "budget_category_name",
            "budget_item", "budget_item_code", "budget_item_name", "budget_item_category",
            "date", "created_at", "updated_at",
        ]
        read_only_fields = [
            "id", "created_at", "updated_at", "project_id", "ledger_label",
            "budget_category_code", "budget_category_name",
            "budget_item_code", "budget_item_name", "budget_item_category",
        ]

    def validate(self, attrs):
        # Expenses must map to a budget item so spend can be measured against allocation.
        entry_type = attrs.get("type", getattr(self.instance, "type", None))
        budget_item = attrs.get("budget_item", getattr(self.instance, "budget_item", None))
        if entry_type == "expense" and budget_item is None:
            raise serializers.ValidationError({"budget_item": "Budget item is required for expenses."})
        return attrs


class ProjectDisseminationSerializer(serializers.ModelSerializer):
    project_id = serializers.CharField(source="project.id", read_only=True)
    event_name = serializers.SerializerMethodField()
    publication_title = serializers.SerializerMethodField()

    class Meta:
        model = ProjectDissemination
        fields = [
            "id", "project", "project_id", "title", "description", "date", "location",
            "event", "event_name", "publication", "publication_title",
            "created_at", "updated_at",
        ]
        read_only_fields = ["id", "created_at", "updated_at", "project_id", "event_name", "publication_title"]

    def get_event_name(self, obj):
        return obj.event.name if obj.event else None

    def get_publication_title(self, obj):
        return obj.publication.title if obj.publication else None


class ProjectResourceSerializer(serializers.ModelSerializer):
    project_id = serializers.CharField(source="project.id", read_only=True)
    created_by_name = serializers.SerializerMethodField()

    class Meta:
        model = ProjectResource
        fields = [
            "id", "project", "project_id", "kind", "title", "link",
            "detail", "source", "year",
            "authors", "publisher", "lit_type", "quoted_page", "notes",
            "created_by", "created_by_name", "created_at", "updated_at",
        ]
        read_only_fields = [
            "id", "project", "project_id", "created_by", "created_by_name",
            "created_at", "updated_at",
        ]

    def get_created_by_name(self, obj):
        if not obj.created_by_id:
            return None
        return obj.created_by.get_full_name() or obj.created_by.email


class ProjectLogSerializer(serializers.ModelSerializer):
    actor_name = serializers.SerializerMethodField()

    class Meta:
        model = ProjectLog
        fields = [
            "id", "project", "log_type", "action", "detail",
            "actor", "actor_name", "created_at",
        ]
        read_only_fields = fields

    def get_actor_name(self, obj):
        if not obj.actor_id:
            return None
        return obj.actor.get_full_name() or obj.actor.email


class ProjectScheduleSerializer(serializers.ModelSerializer):
    project_id = serializers.CharField(source="project.id", read_only=True)

    class Meta:
        model = ProjectSchedule
        fields = ["id", "project", "project_id", "title", "description", "start_date", "end_date", "created_at", "updated_at"]
        read_only_fields = ["id", "created_at", "updated_at", "project_id"]


class ProjectFundSerializer(serializers.ModelSerializer):
    project = serializers.PrimaryKeyRelatedField(
        queryset=Project.objects.all(), required=False, allow_null=True,
    )
    project_id = serializers.CharField(source="project.id", read_only=True, default=None)
    project_name = serializers.CharField(source="project.name", read_only=True, default=None)
    status_label = serializers.CharField(source="get_status_display", read_only=True)
    fund_type_label = serializers.CharField(source="get_fund_type_display", read_only=True)
    donor_id = serializers.CharField(source="donor.id", read_only=True, default=None)
    donor_name = serializers.CharField(source="donor.name", read_only=True, default=None)
    donor_abbreviation = serializers.CharField(source="donor.abbreviation", read_only=True, default=None)
    donor_logo_url = serializers.SerializerMethodField()
    donor_website = serializers.CharField(source="donor.website", read_only=True, default=None)

    class Meta:
        model = ProjectFund
        fields = [
            "id", "project", "project_id", "project_name",
            "source", "donor", "donor_id", "donor_name", "donor_abbreviation", "donor_logo_url", "donor_website",
            "fund_type", "fund_type_label",
            "amount", "currency", "exchange_rate", "amount_idr", "conversion_timing", "status", "status_label",
            "frozen", "agreement_date", "received_date", "notes",
            "created_at", "updated_at",
        ]
        read_only_fields = [
            "id", "created_at", "updated_at", "project_id", "project_name",
            "status_label", "fund_type_label", "donor_id", "donor_name", "donor_abbreviation", "donor_logo_url", "donor_website",
            "exchange_rate", "amount_idr", "frozen",
        ]

    def get_donor_logo_url(self, obj):
        if not obj.donor or not obj.donor.logo:
            return None
        request = self.context.get("request")
        url = obj.donor.logo.url
        return request.build_absolute_uri(url) if request else url


class ProjectFundAllocationSerializer(serializers.ModelSerializer):
    """Allocation of a fund's amount to a budget item."""
    budget_item_code = serializers.CharField(source="budget_item.code", read_only=True)
    budget_item_name = serializers.CharField(source="budget_item.name", read_only=True)
    category_name = serializers.CharField(source="budget_item.category.name", read_only=True, default=None)
    fund_source = serializers.CharField(source="fund.source", read_only=True, default=None)
    fund_currency = serializers.CharField(source="fund.currency", read_only=True, default=None)
    fund_donor_name = serializers.SerializerMethodField()

    class Meta:
        model = ProjectFundAllocation
        fields = [
            "id", "fund", "fund_source", "fund_currency", "fund_donor_name",
            "budget_item", "budget_item_code", "budget_item_name", "category_name",
            "unit", "quantity", "frequency",
            "unit_cost", "unit_cost_currency", "unit_cost_fund_ccy",
            "time_allocated_pct", "amount", "note", "created_at", "updated_at",
        ]
        read_only_fields = [
            "id", "created_at", "updated_at", "amount", "unit_cost_fund_ccy",
            "budget_item_code", "budget_item_name", "category_name",
            "fund_source", "fund_currency", "fund_donor_name",
        ]

    def get_fund_donor_name(self, obj):
        f = obj.fund
        if f and f.donor_id:
            return f.donor.name
        return (f.source if f else None)

class FundingOpportunitySerializer(serializers.ModelSerializer):
    donor_name = serializers.SerializerMethodField()

    class Meta:
        model = FundingOpportunity
        fields = [
            "id", "fund", "name", "donor", "donor_name", "deadline",
            "ceiling", "stage", "notes", "created_at", "updated_at",
        ]
        read_only_fields = ["id", "created_at", "updated_at"]

    def get_donor_name(self, obj):
        return obj.donor.name if obj.donor_id else None


class FundingProposalSerializer(serializers.ModelSerializer):
    class Meta:
        model = FundingProposal
        fields = [
            "id", "fund", "title", "submitted_date", "requested_amount",
            "status", "document", "notes", "created_at", "updated_at",
        ]
        read_only_fields = ["id", "created_at", "updated_at"]


class FundReportSerializer(serializers.ModelSerializer):
    class Meta:
        model = FundReport
        fields = [
            "id", "fund", "title", "kind", "due_date", "submitted_date",
            "status", "document", "notes", "created_at", "updated_at",
        ]
        read_only_fields = ["id", "created_at", "updated_at"]


class ProjectFundTrancheSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProjectFundTranche
        fields = [
            "id", "fund", "label", "amount", "expected_date", "received_date",
            "status", "notes", "created_at", "updated_at",
        ]
        read_only_fields = ["id", "created_at", "updated_at"]


class ProjectFundComplianceSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProjectFundCompliance
        fields = [
            "id", "fund", "title", "kind", "due_date", "status",
            "notes", "created_at", "updated_at",
        ]
        read_only_fields = ["id", "created_at", "updated_at"]


class FundDocumentSerializer(serializers.ModelSerializer):
    category_label = serializers.CharField(source="get_category_display", read_only=True)
    document_url = serializers.SerializerMethodField()

    class Meta:
        model = FundDocument
        fields = [
            "id", "fund", "category", "category_label", "title",
            "document", "document_url", "date", "amount", "status", "notes",
            "created_at", "updated_at",
        ]
        read_only_fields = ["id", "category_label", "document_url", "created_at", "updated_at"]

    def get_document_url(self, obj):
        if not obj.document:
            return None
        request = self.context.get("request")
        url = obj.document.url
        return request.build_absolute_uri(url) if request else url
