from rest_framework import serializers
from .models import JobDescription, JDRecruiterAssignment, JobPosting


class JobPostingSerializer(serializers.ModelSerializer):
    channel_label = serializers.CharField(source="get_channel_display", read_only=True)
    posted_by_email = serializers.EmailField(source="posted_by.email", default=None, read_only=True)

    class Meta:
        model = JobPosting
        fields = [
            "id",
            "channel",
            "channel_label",
            "status",
            "external_post_id",
            "external_url",
            "error_message",
            "posted_by_email",
            "posted_at",
            "updated_at",
        ]


class RecruiterBriefSerializer(serializers.Serializer):
    """Lightweight recruiter representation for dropdowns and assignment chips."""
    id = serializers.IntegerField()
    email = serializers.EmailField()
    full_name = serializers.CharField(allow_blank=True)
    role = serializers.CharField()
    phone = serializers.CharField(allow_blank=True, required=False)
    location = serializers.CharField(allow_blank=True, required=False)
    experience_years = serializers.IntegerField(required=False)


class JobDescriptionSerializer(serializers.ModelSerializer):
    created_by_email = serializers.EmailField(source="created_by.email", read_only=True)
    created_by_name = serializers.SerializerMethodField()

    def get_created_by_name(self, obj):
        u = obj.created_by
        if not u:
            return None
        return u.full_name or u.email
    client_name = serializers.CharField(source="client.name", read_only=True)
    assigned_recruiters = RecruiterBriefSerializer(many=True, read_only=True)
    postings = JobPostingSerializer(many=True, read_only=True)
    candidates_count = serializers.SerializerMethodField()

    def get_candidates_count(self, obj):
        # List/retrieve annotate this in one SQL pass (see JobDescriptionViewSet
        # .get_queryset). Fall back to a COUNT only for un-annotated instances
        # (e.g. the object returned straight after create/update).
        annotated = getattr(obj, "candidates_count", None)
        if annotated is not None:
            return annotated
        return obj.applications.count()

    current_approver_name = serializers.SerializerMethodField()
    approved_by_name = serializers.SerializerMethodField()
    jd_code = serializers.SerializerMethodField()
    jd_status = serializers.SerializerMethodField()
    publication_status = serializers.SerializerMethodField()

    def get_publication_status(self, obj):
        """Publish/posting state of the JD, kept separate from the lifecycle
        status: "draft" until the JD has actually been posted to at least one
        channel, "published" afterwards.

        Derived from the existing JobPosting rows — no stored field changes, and
        no extra query (the viewset prefetches `postings`, which the nested
        `postings` field reads too).
        """
        posted = JobPosting.Status.POSTED
        return "published" if any(p.status == posted for p in obj.postings.all()) else "draft"

    def get_jd_status(self, obj):
        """Single canonical JD status derived from the existing lifecycle fields.

        The UI shows exactly one badge based on this value, so the legacy
        `status` (Draft/Published/Closed) and `approval_status`
        (DRAFT/PENDING_APPROVAL/APPROVED/REJECTED) can no longer disagree
        on-screen. The underlying fields are untouched (all filters, counters,
        permissions and workflows keep working); this only collapses them into
        one presentation value.

            draft            -> not yet published / rejected (returns to draft)
            pending_approval -> submitted, awaiting a decision
            published        -> approved and live
            closed           -> retired (preserves existing Closed lifecycle)
        """
        # Published (approval auto-publishes) wins — a published JD never shows
        # an approval sub-status. Mirrors the to_representation() coercion below.
        if obj.status == "Published":
            return "published"
        if obj.status == "Closed":
            return "closed"
        if obj.approval_status == "PENDING_APPROVAL":
            return "pending_approval"
        # DRAFT, REJECTED (returns to draft), or any unknown value.
        return "draft"

    def get_current_approver_name(self, obj):
        u = obj.current_approver
        if not u:
            return None
        return u.full_name or u.email

    def get_approved_by_name(self, obj):
        u = obj.approved_by
        if not u:
            return None
        return u.full_name or u.email

    def get_jd_code(self, obj):
        return f"JD-{obj.id:04d}"

    short_code = serializers.SerializerMethodField()

    def get_short_code(self, obj):
        # Short, obfuscated code for compact public/WhatsApp links
        # (/careers/jobs/<short_code>) — hides the sequential id.
        from core.crypto import short_encode_id
        return short_encode_id(obj.id)

    class Meta:
        model = JobDescription
        fields = [
            "id",
            "title",
            "department",
            "location",
            "experience_band",
            "ctc_band",
            "notice_period",
            "shift",
            "must_have_skills",
            "good_to_have_skills",
            "qualifications",
            "working_days",
            "num_positions",
            "certification",
            "questions",
            "rank_weights",
            "attachment",
            "work_details",
            "status",
            "priority",
            "created_by",
            "created_by_email",
            "created_by_name",
            "client",
            "client_name",
            "assigned_recruiters",
            "postings",
            "candidates_count",
            "created_at",
            "updated_at",
            "approval_status",
            "submitted_for_approval_at",
            "approved_at",
            "rejected_at",
            "approved_by",
            "approved_by_name",
            "rejection_reason",
            "current_approver",
            "current_approver_name",
            "jd_code",
            "jd_status",
            "publication_status",
            "short_code",
            "hunar_agent_id",
        ]
        read_only_fields = ["id", "created_by", "created_at", "updated_at", "assigned_recruiters", "postings", "jd_code", "jd_status", "publication_status", "current_approver_name", "approved_by_name"]

    def validate(self, attrs):
        """Prevent duplicate JDs and enforce the JD lifecycle rules.

        Allowed transitions are constrained to the existing ATS workflow:
        Draft -> pending approval -> approved -> Published, and after that only
        Published -> Closed. Manual Published -> Draft requests are rejected.
        """
        if "status" in attrs:
            current_status = getattr(self.instance, "status", "Draft") if self.instance else "Draft"
            new_status = attrs["status"]

            if self.instance is None:
                # New JDs always start as Draft — publishing happens via approval.
                if new_status != "Draft":
                    raise serializers.ValidationError(
                        {"status": "New Job Descriptions start as Draft. Status can only be changed after the JD has been approved."}
                    )
            else:
                if current_status == "Published" and new_status != "Closed":
                    raise serializers.ValidationError(
                        {"status": "A Published Job Description can only be changed to Closed."}
                    )

                if current_status == "Closed" and new_status != "Closed":
                    raise serializers.ValidationError(
                        {"status": "A Closed Job Description cannot be changed to another status."}
                    )

                # Published JDs predating the approval flow count as approved.
                approved = self.instance.approval_status == "APPROVED" or self.instance.status == "Published"
                if new_status != current_status and not approved:
                    raise serializers.ValidationError(
                        {"status": "Status can only be changed after the Job Description has been approved."}
                    )

        # On partial update, fall back to the existing instance values.
        title = (attrs.get("title") if "title" in attrs
                 else getattr(self.instance, "title", "")) or ""
        client = attrs.get("client") if "client" in attrs else getattr(self.instance, "client", None)

        # Associated Client is mandatory (skip on partial update when client isn't being changed,
        # e.g. the multipart PATCH that attaches the JD file).
        if client is None and not (self.partial and "client" not in attrs):
            raise serializers.ValidationError({"client": "Associated Client is required."})

        # JD titles must be unique on their own (case-insensitive) — not just when the
        # client and location also match — so "Google" and "google" can't both exist.
        if title.strip():
            qs = JobDescription.objects.filter(title__iexact=title.strip())
            if self.instance:
                qs = qs.exclude(pk=self.instance.pk)
            if qs.exists():
                raise serializers.ValidationError(
                    {"title": "A job description with this title already exists."}
                )
        return attrs

    def to_representation(self, instance):
        ret = super().to_representation(instance)
        # Published JDs predating the approval flow count as approved. We do NOT
        # touch the raw `status` (the app relies on Draft/Published/Closed) nor
        # overwrite a real approval_status — the single canonical value is
        # exposed separately as `jd_status` (see get_jd_status).
        if instance.status == "Published":
            ret["approval_status"] = "APPROVED"
        return ret



class AssignedJDSerializer(serializers.ModelSerializer):
    """A JD as seen by an assigned recruiter — flattens the assignment row + JD.

    Instantiated with a JDRecruiterAssignment instance.
    """
    jd_id = serializers.IntegerField(source="jd.id")
    title = serializers.CharField(source="jd.title")
    location = serializers.CharField(source="jd.location")
    client_name = serializers.CharField(source="jd.client.name", default=None)
    priority = serializers.CharField(source="jd.priority")
    status = serializers.CharField(source="jd.status")

    class Meta:
        model = JDRecruiterAssignment
        fields = [
            "jd_id",
            "title",
            "location",
            "client_name",
            "priority",
            "status",
            "assigned_at",
        ]


from .models import JDApprovalHistory

class JDApprovalHistorySerializer(serializers.ModelSerializer):
    job_title = serializers.CharField(source="job_description.title", read_only=True)
    jd_code = serializers.SerializerMethodField()
    action_by_name = serializers.SerializerMethodField()

    def get_jd_code(self, obj):
        jd = obj.job_description
        return f"JD-{jd.id:04d}"

    def get_action_by_name(self, obj):
        u = obj.action_by
        if not u:
            return "System"
        return u.full_name or u.email

    class Meta:
        model = JDApprovalHistory
        fields = [
            "id",
            "job_description",
            "job_title",
            "jd_code",
            "action",
            "action_by",
            "action_by_name",
            "remarks",
            "previous_status",
            "new_status",
            "created_at",
        ]
