from rest_framework import serializers
from django.contrib.auth import get_user_model
from typing import Any, Dict

from .models import Candidate, Skill, Language, CandidateProject, CandidateDocument, CandidateReference, CandidateExperience, CandidateEducation, CandidateAttachment
from .validators import validate_phone_number, validate_passing_year, validate_non_negative, validate_resume_file
from apps.master_data.models import NoticePeriod

User = get_user_model()


class CandidateExperienceSerializer(serializers.ModelSerializer):
    """Serializer for CandidateExperience model."""
    class Meta:
        model = CandidateExperience
        fields = [
            "id", "company_name", "role", "joining_date", "last_working_date",
            "is_current_company", "current_ctc", "expected_ctc", "notice_period",
            "achievements", "responsibilities", "reason_for_leaving"
        ]
        extra_kwargs = {
            "last_working_date": {"required": False, "allow_null": True},
            "current_ctc": {"required": False, "allow_null": True},
            "expected_ctc": {"required": False, "allow_null": True},
            "notice_period": {"required": False, "allow_null": True},
            "achievements": {"required": False, "allow_null": True, "allow_blank": True},
            "responsibilities": {"required": False, "allow_null": True, "allow_blank": True},
            "reason_for_leaving": {"required": False, "allow_null": True, "allow_blank": True},
        }


class CandidateEducationSerializer(serializers.ModelSerializer):
    """Serializer for CandidateEducation model."""
    class Meta:
        model = CandidateEducation
        fields = ["id", "degree_name", "field_of_study", "institution_name", "passing_year", "percentage_cgpa"]



class CandidateProjectSerializer(serializers.ModelSerializer):
    """Serializer for CandidateProject model."""
    class Meta:
        model = CandidateProject
        fields = ["id", "project_name", "description", "technologies_used", "duration", "role"]


class CandidateReferenceSerializer(serializers.ModelSerializer):
    """Serializer for CandidateReference model."""
    class Meta:
        model = CandidateReference
        fields = ["id", "name", "company", "designation", "email", "phone", "relationship"]
        extra_kwargs = {
            "email": {"required": False, "allow_blank": True},
            "phone": {"required": False, "allow_blank": True},
        }


class CandidateDocumentSerializer(serializers.ModelSerializer):
    """Serializer for CandidateDocument model."""
    class Meta:
        model = CandidateDocument
        fields = ["id", "document_type", "file", "verification_status"]
        read_only_fields = ["verification_status"]


class CandidateAttachmentSerializer(serializers.ModelSerializer):
    """OpenCATS-style attachment metadata + a signed, private download URL."""
    kind_label = serializers.CharField(source="get_kind_display", read_only=True)
    uploaded_by_email = serializers.CharField(source="uploaded_by.email", read_only=True, default=None)
    download_url = serializers.SerializerMethodField()

    class Meta:
        model = CandidateAttachment
        fields = [
            "id", "kind", "kind_label", "original_filename", "content_type",
            "size", "is_primary", "created_at", "uploaded_by_email", "download_url",
        ]

    def get_download_url(self, obj):
        from .resume_storage import signed_resume_url
        req = self.context.get("request")
        return signed_resume_url(req, obj.file.name) if obj.file else None


class CandidateListSerializer(serializers.ModelSerializer):
    """Compact serializer for listing candidates. PII (phone/email) is MASKED
    here — the list is a bulk view; full values are shown only in the detail
    serializer to authorized users (DPDP data-minimization)."""
    email = serializers.SerializerMethodField()
    phone_number = serializers.SerializerMethodField()
    full_name = serializers.SerializerMethodField()
    skills = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")
    resume = serializers.SerializerMethodField()
    resume_url = serializers.SerializerMethodField()

    class Meta:
        model = Candidate
        fields = [
            "id",
            "full_name",
            "first_name",
            "last_name",
            "email",
            "telegram_chat_id",
            "whatsapp_number",
            "phone_number",
            "date_of_birth",
            "gender",
            "fresher",
            "total_experience",
            "employment_type",
            "current_company",
            "current_role",
            "previous_company",
            "current_ctc",
            "notice_period",
            "highest_qualification",
            "university",
            "passing_year",
            "preferred_location",
            "city",
            "state",
            "skills",
            "status",
            "source",
            "resume",
            "resume_url",
            "created_at",
            "updated_at",
        ]

    def get_full_name(self, obj: Candidate) -> str:
        return f"{obj.first_name} {obj.last_name}"

    def get_email(self, obj: Candidate):
        from core.masking import mask_email
        return mask_email(obj.email or (obj.user.email if obj.user_id else None))

    def get_phone_number(self, obj: Candidate):
        from core.masking import mask_phone
        return mask_phone(obj.phone_number)

    def get_resume(self, obj: Candidate):
        return obj.resume.name if obj.resume else None

    def get_resume_url(self, obj: Candidate):
        from .resume_storage import signed_resume_url
        return signed_resume_url(self.context.get("request"), obj.resume.name) if obj.resume else None


class DeletedCandidateListSerializer(serializers.ModelSerializer):
    """Read-only serializer for the admin-only Draft view of soft-deleted
    candidates. Mirrors the active-list columns and adds deletion metadata.

    The model has no dedicated deleted_at/deleted_by columns; for a soft-deleted
    row the last write IS the deletion, so `updated_at`/`updated_by` are exposed
    as the deletion timestamp and actor.
    """

    email = serializers.SerializerMethodField()
    full_name = serializers.SerializerMethodField()
    skills = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")
    has_resume = serializers.SerializerMethodField()
    deleted_at = serializers.DateTimeField(source="updated_at", read_only=True)
    deleted_by = serializers.SerializerMethodField()

    class Meta:
        model = Candidate
        fields = [
            "id",
            "full_name",
            "first_name",
            "last_name",
            "email",
            "phone_number",
            "skills",
            "fresher",
            "total_experience",
            "status",
            "resume",
            "has_resume",
            "created_at",
            "deleted_at",
            "deleted_by",
        ]

    def get_full_name(self, obj: Candidate) -> str:
        return f"{obj.first_name} {obj.last_name}"

    def get_email(self, obj: Candidate):
        return obj.email or (obj.user.email if obj.user_id else None)

    def get_has_resume(self, obj: Candidate) -> bool:
        return bool(obj.resume)

    def get_deleted_by(self, obj: Candidate):
        u = obj.updated_by
        if not u:
            return None
        return getattr(u, "full_name", "") or getattr(u, "email", "") or None


class CandidateDetailSerializer(serializers.ModelSerializer):
    """Full detail serializer for Candidate profile."""
    email = serializers.SerializerMethodField()
    resume = serializers.SerializerMethodField()
    resume_url = serializers.SerializerMethodField()
    skills = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")
    languages = serializers.SlugRelatedField(many=True, read_only=True, slug_field="name")
    projects = CandidateProjectSerializer(many=True, read_only=True)
    experiences = CandidateExperienceSerializer(many=True, read_only=True)
    educations = CandidateEducationSerializer(many=True, read_only=True)
    references = CandidateReferenceSerializer(many=True, read_only=True)
    documents = CandidateDocumentSerializer(many=True, read_only=True)
    created_by_email = serializers.EmailField(source="created_by.email", read_only=True)
    updated_by_email = serializers.EmailField(source="updated_by.email", read_only=True)
    notice_period_id = serializers.IntegerField(source="notice_period_ref_id", read_only=True)
    notice_period_name = serializers.CharField(source="notice_period_ref.label", read_only=True, default=None)
    # OUT-007: read-only email subscription status, from the existing EmailOptOut
    # list. Lets authorized users see whether outreach mail will reach this
    # candidate. Read-only — unsubscribing stays a candidate-driven action.
    email_subscribed = serializers.SerializerMethodField()
    # Job Orders this candidate is assigned to (via pipeline applications) —
    # used to prefill the edit form's job picker and show current assignments.
    assigned_job_ids = serializers.SerializerMethodField()
    assigned_jobs = serializers.SerializerMethodField()

    def get_assigned_job_ids(self, obj):
        return list(obj.applications.values_list("job_id", flat=True).distinct())

    def get_assigned_jobs(self, obj):
        seen, out = set(), []
        for app in obj.applications.select_related("job").all():
            if app.job_id and app.job_id not in seen:
                seen.add(app.job_id)
                out.append({"id": app.job_id, "title": app.job.title})
        return out

    def get_email_subscribed(self, obj):
        from .models import EmailOptOut
        email = (obj.email or "").strip()
        if not email:
            return None          # no address on file — nothing to subscribe
        return not EmailOptOut.objects.filter(email__iexact=email).exists()

    class Meta:
        model = Candidate
        fields = [
            "id",
            "email",
            "email_subscribed",
            "telegram_chat_id",
            "whatsapp_number",
            "first_name",
            "last_name",
            "phone_number",
            "alternate_phone_number",
            "date_of_birth",
            "gender",
            "city",
            "state",
            "country",
            "current_address",
            "permanent_address",
            "fresher",
            "total_experience",
            "employment_type",
            "current_company",
            "previous_company",
            "current_role",
            "current_ctc",
            "expected_ctc",
            "notice_period",
            "notice_period_id",
            "notice_period_name",
            "current_location",
            "preferred_location",
            "highest_qualification",
            "university",
            "college",
            "passing_year",
            "percentage_cgpa",
            "skills",
            "languages",
            "projects",
            "experiences",
            "educations",
            "references",
            "documents",
            "professional_summary",
            "resume",
            "resume_url",
            "availability",
            "linkedin",
            "github",
            "portfolio",
            "personal_website",
            "status",
            "source",
            "created_at",
            "updated_at",
            "created_by_email",
            "updated_by_email",
            "is_active",
            "assigned_job_ids",
            "assigned_jobs",
        ]

    def get_email(self, obj: Candidate):
        return obj.email or (obj.user.email if obj.user_id else None)

    def get_resume(self, obj: Candidate):
        return obj.resume.name if obj.resume else None

    def get_resume_url(self, obj: Candidate):
        from .resume_storage import signed_resume_url
        return signed_resume_url(self.context.get("request"), obj.resume.name) if obj.resume else None


class ResumeField(serializers.FileField):
    """Custom FileField serializer to support both UploadedFile objects and string paths (already uploaded)."""
    def to_internal_value(self, data):
        if not data:
            return None
        if isinstance(data, str):
            # A display URL (signed download link) echoed back from the client must
            # NOT overwrite the stored file path — skip the field so the file is kept.
            if data.startswith("http") or "/resume/" in data or "/media/" in data:
                from rest_framework.fields import SkipField
                raise SkipField()
            return data

        from django.core.files.uploadedfile import UploadedFile
        if hasattr(data, 'name') or isinstance(data, UploadedFile):
            validate_resume_file(data)
            return super().to_internal_value(data)

        raise serializers.ValidationError("Invalid resume file format.")

    def to_representation(self, value):
        if not value:
            return None
        if hasattr(value, 'url'):
            try:
                return value.url
            except ValueError:
                return str(value)
        return str(value)


class CandidateCreateUpdateSerializer(serializers.Serializer):
    """Serializer for validating candidate creation and updates."""
    # Basic info
    first_name = serializers.CharField(max_length=100)
    last_name = serializers.CharField(max_length=100)
    email = serializers.EmailField(required=False, allow_null=True, allow_blank=True)
    telegram_chat_id = serializers.CharField(max_length=64, required=False, allow_null=True, allow_blank=True)
    whatsapp_number = serializers.CharField(max_length=32, required=False, allow_null=True, allow_blank=True)
    phone_number = serializers.CharField(max_length=20, validators=[validate_phone_number])
    alternate_phone_number = serializers.CharField(max_length=20, validators=[validate_phone_number], required=False, allow_null=True, allow_blank=True)
    date_of_birth = serializers.DateField(required=False, allow_null=True)
    gender = serializers.CharField(max_length=20, required=False, allow_null=True, allow_blank=True)
    city = serializers.CharField(max_length=100, required=False, allow_null=True, allow_blank=True)
    state = serializers.CharField(max_length=100, required=False, allow_null=True, allow_blank=True)
    country = serializers.CharField(max_length=100, required=False, allow_null=True, allow_blank=True)
    current_address = serializers.CharField(required=False, allow_null=True, allow_blank=True)
    permanent_address = serializers.CharField(required=False, allow_null=True, allow_blank=True)

    # Professional Info
    fresher = serializers.BooleanField(default=True)
    total_experience = serializers.DecimalField(max_digits=4, decimal_places=1, validators=[validate_non_negative], required=False, allow_null=True)
    employment_type = serializers.ChoiceField(choices=Candidate.EmploymentTypeChoices.choices, required=False, allow_null=True, allow_blank=True)
    current_company = serializers.CharField(max_length=255, required=False, allow_null=True, allow_blank=True)
    previous_company = serializers.CharField(max_length=255, required=False, allow_null=True, allow_blank=True)
    current_role = serializers.CharField(max_length=255, required=False, allow_null=True, allow_blank=True)
    current_ctc = serializers.DecimalField(max_digits=12, decimal_places=2, validators=[validate_non_negative], required=False, allow_null=True)
    expected_ctc = serializers.DecimalField(max_digits=12, decimal_places=2, validators=[validate_non_negative], required=False, allow_null=True)
    notice_period = serializers.IntegerField(validators=[validate_non_negative], required=False, allow_null=True)
    # Foreign-key reference to the Notice Period master (the value saved from the profile dropdown).
    notice_period_id = serializers.PrimaryKeyRelatedField(
        queryset=NoticePeriod.objects.all(),
        source="notice_period_ref",
        required=False,
        allow_null=True,
    )
    current_location = serializers.CharField(max_length=100, required=False, allow_null=True, allow_blank=True)
    preferred_location = serializers.CharField(max_length=500, required=False, allow_null=True, allow_blank=True)

    # Education
    highest_qualification = serializers.CharField(max_length=255, required=False, allow_null=True, allow_blank=True)
    university = serializers.CharField(max_length=255, required=False, allow_null=True, allow_blank=True)
    college = serializers.CharField(max_length=255, required=False, allow_null=True, allow_blank=True)
    passing_year = serializers.IntegerField(validators=[validate_passing_year], required=False, allow_null=True)
    percentage_cgpa = serializers.CharField(max_length=50, required=False, allow_null=True, allow_blank=True)

    # Skills & Languages
    skills = serializers.ListField(child=serializers.CharField(max_length=100), required=False, default=[])
    languages = serializers.ListField(child=serializers.CharField(max_length=100), required=False, default=[])

    # Projects, References, Experiences & Educations
    projects = CandidateProjectSerializer(many=True, required=False, default=[])
    references = CandidateReferenceSerializer(many=True, required=False, default=[])
    experiences = CandidateExperienceSerializer(many=True, required=False, default=[])
    educations = CandidateEducationSerializer(many=True, required=False, default=[])

    # Resume, summary, availability
    professional_summary = serializers.CharField(required=False, allow_null=True, allow_blank=True)
    resume = ResumeField(required=False, allow_null=True)
    availability = serializers.ChoiceField(choices=Candidate.AvailabilityChoices.choices, default=Candidate.AvailabilityChoices.IMMEDIATE)
    
    # Socials
    linkedin = serializers.URLField(required=False, allow_null=True, allow_blank=True)
    github = serializers.URLField(required=False, allow_null=True, allow_blank=True)
    portfolio = serializers.URLField(required=False, allow_null=True, allow_blank=True)
    personal_website = serializers.URLField(required=False, allow_null=True, allow_blank=True)

    # Status
    status = serializers.ChoiceField(choices=Candidate.StatusChoices.choices, default=Candidate.StatusChoices.DRAFT)
    source = serializers.ChoiceField(choices=Candidate.SourceChoices.choices, required=False)

    def validate(self, attrs: Dict[str, Any]) -> Dict[str, Any]:
        """Perform conditional validations based on Fresher status + duplicate checks."""
        errors = {}

        # Keep the legacy integer `notice_period` (days) in sync with the selected
        # Notice Period master reference, so matching/bulk/display consumers keep working.
        if "notice_period_ref" in attrs:
            ref = attrs.get("notice_period_ref")
            attrs["notice_period"] = ref.value if ref is not None else None

        # --- Duplicate email / phone check (exclude self when updating) ---
        import re as _re
        current_id = self.instance.id if self.instance else None

        email = attrs.get("email")
        if email:
            qs = Candidate.objects.filter(email__iexact=email.strip())
            if current_id:
                qs = qs.exclude(id=current_id)
            if qs.exists():
                errors["email"] = "A candidate with this email already exists."

        telegram_chat_id = attrs.get("telegram_chat_id")
        if telegram_chat_id:
            qs = Candidate.objects.filter(telegram_chat_id=telegram_chat_id)
            if current_id:
                qs = qs.exclude(id=current_id)
            if qs.exists():
                errors["telegram_chat_id"] = "A candidate is already linked to this Telegram chat."

        whatsapp_number = attrs.get("whatsapp_number")
        if whatsapp_number:
            qs = Candidate.objects.filter(whatsapp_number=whatsapp_number)
            if current_id:
                qs = qs.exclude(id=current_id)
            if qs.exists():
                errors["whatsapp_number"] = "A candidate is already linked to this WhatsApp number."

        phone = attrs.get("phone_number")
        if phone:
            digits = _re.sub(r"\D", "", phone)
            if digits:
                # match by digits so +91/spaces/dashes don't create false uniques
                qs = Candidate.objects.filter(phone_number__contains=digits[-10:]) if len(digits) >= 10 \
                    else Candidate.objects.filter(phone_number=phone)
                if current_id:
                    qs = qs.exclude(id=current_id)
                if qs.exists():
                    errors["phone_number"] = "A candidate with this contact number already exists."

        # SRC-006: name is part of the duplicate key — flag an exact full-name match
        # that ALSO shares an email or phone (same person entered twice via a different source).
        first = (attrs.get("first_name") or "").strip()
        last = (attrs.get("last_name") or "").strip()
        if first and last and "email" not in errors and "phone_number" not in errors:
            name_qs = Candidate.objects.filter(first_name__iexact=first, last_name__iexact=last)
            if current_id:
                name_qs = name_qs.exclude(id=current_id)
            same_person = False
            if email:
                same_person = name_qs.filter(email__iexact=email.strip()).exists()
            if not same_person and phone:
                d = _re.sub(r"\D", "", phone)
                if d:
                    same_person = name_qs.filter(phone_number__contains=d[-10:]).exists()
            if same_person:
                errors["first_name"] = "A candidate with this name + email/phone already exists (possible duplicate)."

        # --- Conditional validation for non-freshers ---
        fresher = attrs.get("fresher", True)
        if not fresher:
            if attrs.get("total_experience") is None:
                errors["total_experience"] = "Total experience is required for non-fresher candidates."
            if not attrs.get("current_role"):
                errors["current_role"] = "Current role is required for non-fresher candidates."
            if not attrs.get("current_location"):
                errors["current_location"] = "Current location is required for non-fresher candidates."

        if errors:
            raise serializers.ValidationError(errors)
        return attrs
