import secrets
from datetime import date as date_cls

from django.contrib.auth import get_user_model
from django.db import transaction
from rest_framework import serializers

from .models import (
    Employee, Attendance, Payroll, Recruitment, JobApplication, Onboarding, Contract,
    Communication, Survey, SurveyResponse, Family, AttendanceSettings, ShiftTemplate,
    PayrollSettings, DEFAULT_ATTENDANCE_SETTINGS, DEFAULT_PAYROLL_SETTINGS,
)


class AttendanceSettingsSerializer(serializers.ModelSerializer):
    class Meta:
        model = AttendanceSettings
        fields = ["settings", "updated_at"]
        read_only_fields = ["updated_at"]

    def to_representation(self, instance):
        base = dict(DEFAULT_ATTENDANCE_SETTINGS)
        merged = {**base, **(instance.settings or {})}
        return merged

    def update(self, instance, validated_data):
        # Accept any keys; clients send full nested object
        payload = self.context["request"].data if self.context.get("request") else validated_data.get("settings", {})
        instance.settings = dict(payload)
        instance.save(update_fields=["settings", "updated_at"])
        return instance


class PayrollSettingsSerializer(serializers.ModelSerializer):
    class Meta:
        model = PayrollSettings
        fields = ["settings", "updated_at"]
        read_only_fields = ["updated_at"]

    def to_representation(self, instance):
        import copy
        base = copy.deepcopy(DEFAULT_PAYROLL_SETTINGS)
        def deep_merge(base, override):
            for k, v in override.items():
                if isinstance(v, dict) and isinstance(base.get(k), dict):
                    deep_merge(base[k], v)
                else:
                    base[k] = v
        deep_merge(base, instance.settings or {})
        return base

    def update(self, instance, validated_data):
        payload = self.context["request"].data if self.context.get("request") else validated_data.get("settings", {})
        instance.settings = dict(payload)
        instance.save(update_fields=["settings", "updated_at"])
        return instance


class ShiftTemplateSerializer(serializers.ModelSerializer):
    member_ids = serializers.ListField(
        child=serializers.CharField(),
        required=False,
        write_only=True,
    )
    members = serializers.SerializerMethodField()

    class Meta:
        model = ShiftTemplate
        fields = [
            "id", "name", "start_time", "end_time", "working_days",
            "flex_enabled", "members", "member_ids",
            "created_at", "updated_at",
        ]
        read_only_fields = ["id", "members", "created_at", "updated_at"]

    def get_members(self, obj):
        return [str(emp.id) for emp in obj.members.all()]

    def to_representation(self, obj):
        data = super().to_representation(obj)
        data["member_ids"] = data.pop("members", [])
        return data

    def create(self, validated_data):
        member_ids = validated_data.pop("member_ids", [])
        instance = ShiftTemplate.objects.create(**validated_data)
        if member_ids:
            instance.members.set(Employee.objects.filter(id__in=member_ids))
        return instance

    def update(self, instance, validated_data):
        member_ids = validated_data.pop("member_ids", None)
        for k, v in validated_data.items():
            setattr(instance, k, v)
        instance.save()
        if member_ids is not None:
            instance.members.set(Employee.objects.filter(id__in=member_ids))
        return instance


def _generate_employee_id() -> str:
    today = date_cls.today()
    prefix = f"EMP-{today.year}{today.month:02d}-"
    last = (
        Employee.objects.filter(employee_id__startswith=prefix)
        .order_by("-employee_id")
        .values_list("employee_id", flat=True)
        .first()
    )
    seq = int(last.split("-")[-1]) + 1 if last else 1
    return f"{prefix}{seq:03d}"


class EmployeeSerializer(serializers.ModelSerializer):
    """Serializer for Employee model."""
    user_email = serializers.EmailField(source="user.email", read_only=True)
    user_name = serializers.SerializerMethodField()
    user_phone = serializers.CharField(source="user.phone", read_only=True)
    avatar = serializers.SerializerMethodField()
    contract_end_date = serializers.SerializerMethodField()

    name = serializers.CharField(write_only=True, required=False)
    email = serializers.EmailField(write_only=True, required=False)
    phone = serializers.CharField(write_only=True, required=False, allow_blank=True)
    start_date = serializers.DateField(write_only=True, required=False)
    temp_password = serializers.CharField(read_only=True)

    class Meta:
        model = Employee
        fields = [
            "id", "user", "user_email", "user_name", "user_phone", "avatar", "contract_end_date", "employee_id",
            "employment_type", "position", "department", "category", "phone_extension",
            "status", "hire_date", "termination_date", "salary",
            "emergency_contact_name", "emergency_contact_phone", "address", "notes",
            "payroll_overrides",
            "created_at", "updated_at",
            "name", "email", "phone", "start_date", "temp_password",
        ]
        read_only_fields = ["id", "user", "employee_id", "created_at", "updated_at", "temp_password"]
        extra_kwargs = {
            "hire_date": {"required": False},
            "position": {"required": False, "allow_blank": True},
            "department": {"required": False, "allow_blank": True},
            "category": {"required": False, "allow_blank": True},
            "phone_extension": {"required": False, "allow_blank": True},
        }

    def get_contract_end_date(self, obj):
        contract = next(
            (c for c in obj.contracts.all() if c.status == "active" and c.end_date),
            None
        )
        return contract.end_date.isoformat() if contract else None

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

    def get_avatar(self, obj):
        contact = getattr(obj.user, "contact_profile", None)
        if contact and contact.avatar:
            request = self.context.get("request")
            url = contact.avatar.url
            return request.build_absolute_uri(url) if request else url
        return None

    @transaction.atomic
    def create(self, validated_data):
        name = validated_data.pop("name", "").strip()
        email = validated_data.pop("email", "").strip().lower()
        phone = validated_data.pop("phone", "")
        start_date = validated_data.pop("start_date", None)

        if not email:
            raise serializers.ValidationError({"email": "Email required."})
        if not name:
            raise serializers.ValidationError({"name": "Name required."})

        User = get_user_model()
        if User.objects.filter(email__iexact=email).exists():
            raise serializers.ValidationError({"email": "User with this email already exists."})

        first, _, last = name.partition(" ")
        temp_password = secrets.token_urlsafe(9)

        user = User(
            email=email,
            first_name=first,
            last_name=last,
            phone=phone,
            must_change_password=True,
            onboarding_completed=False,
        )
        user.set_password(temp_password)
        user.save()

        employee = Employee.objects.create(
            user=user,
            employee_id=_generate_employee_id(),
            position=validated_data.get("position", "") or "",
            department=validated_data.get("department", "") or "",
            phone_extension=validated_data.get("phone_extension", "") or "",
            status=validated_data.get("status", "active"),
            hire_date=start_date or validated_data.get("hire_date") or date_cls.today(),
            salary=validated_data.get("salary"),
            emergency_contact_name=validated_data.get("emergency_contact_name", ""),
            emergency_contact_phone=validated_data.get("emergency_contact_phone", ""),
            address=validated_data.get("address", ""),
            notes=validated_data.get("notes", ""),
        )
        employee.temp_password = temp_password
        return employee


class AttendanceSerializer(serializers.ModelSerializer):
    """Serializer for Attendance model."""
    employee_name = serializers.SerializerMethodField()
    photo = serializers.ImageField(read_only=True)

    class Meta:
        model = Attendance
        fields = [
            "id", "employee", "employee_name", "type", "date", "time",
            "location", "photo", "face_match_score", "notes", "created_at",
        ]
        read_only_fields = ["id", "created_at", "photo", "face_match_score"]

    def get_employee_name(self, obj):
        return str(obj.employee)


class PayrollSerializer(serializers.ModelSerializer):
    """Serializer for Payroll model."""
    employee_name = serializers.SerializerMethodField()
    employee_code = serializers.SerializerMethodField()
    employee_position = serializers.SerializerMethodField()
    employee_department = serializers.SerializerMethodField()

    class Meta:
        model = Payroll
        fields = [
            "id", "employee", "employee_name", "employee_code",
            "employee_position", "employee_department",
            "period_start", "period_end",
            "base_salary", "allowances", "deductions", "net_salary", "breakdown", "status",
            "approved_by", "paid_at", "created_at"
        ]
        read_only_fields = ["id", "created_at", "paid_at"]

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

    def get_employee_code(self, obj):
        return obj.employee.employee_id

    def get_employee_position(self, obj):
        return obj.employee.position or ""

    def get_employee_department(self, obj):
        return obj.employee.department or ""


class RecruitmentSerializer(serializers.ModelSerializer):
    """Serializer for Recruitment model."""
    hiring_manager_name = serializers.SerializerMethodField()
    applicant_count = serializers.IntegerField(source="applications.count", read_only=True)
    # Frontend uses "type"; model stores "employment_type".
    type = serializers.CharField(source="employment_type", required=False)

    class Meta:
        model = Recruitment
        fields = [
            "id", "position", "department", "type", "location", "salary_range", "deadline",
            "description", "requirements",
            "status", "hiring_manager", "hiring_manager_name", "applicant_count",
            "created_at", "updated_at"
        ]
        read_only_fields = ["id", "created_at", "updated_at"]

    def get_hiring_manager_name(self, obj):
        if obj.hiring_manager:
            return obj.hiring_manager.get_full_name() or obj.hiring_manager.username
        return None


class JobApplicationSerializer(serializers.ModelSerializer):
    """Admin-side serializer for applications (full read + stage/notes edit)."""
    class Meta:
        model = JobApplication
        fields = [
            "id", "recruitment", "full_name", "email", "phone", "resume",
            "writing_sample", "photo", "cover_letter", "stage", "notes",
            "converted_employee", "created_at", "updated_at"
        ]
        read_only_fields = ["id", "converted_employee", "created_at", "updated_at"]


class PublicRecruitmentSerializer(serializers.ModelSerializer):
    """Read-only posting view for the public careers pages (open postings only)."""
    type = serializers.CharField(source="employment_type", read_only=True)

    class Meta:
        model = Recruitment
        fields = ["id", "position", "department", "type", "location", "salary_range", "deadline",
                  "description", "requirements", "status", "created_at"]


class PublicApplicationSerializer(serializers.ModelSerializer):
    """Public submit serializer. Candidate cannot set stage/notes/recruitment-relations freely."""
    class Meta:
        model = JobApplication
        fields = ["id", "full_name", "email", "phone", "resume", "writing_sample", "photo", "cover_letter"]
        read_only_fields = ["id"]


class OnboardingSerializer(serializers.ModelSerializer):
    """Serializer for Onboarding model."""
    employee_name = serializers.SerializerMethodField()

    class Meta:
        model = Onboarding
        fields = ["id", "employee", "employee_name", "stage", "start_date", "end_date", "tasks", "checklist", "created_at", "updated_at"]
        read_only_fields = ["id", "created_at", "updated_at"]

    def get_employee_name(self, obj):
        return str(obj.employee)


class ContractSerializer(serializers.ModelSerializer):
    """Serializer for Contract model."""
    employee_name = serializers.SerializerMethodField()
    employee_code = serializers.CharField(source="employee.employee_id", read_only=True)
    position = serializers.CharField(source="employee.position", read_only=True)
    days_remaining = serializers.SerializerMethodField()

    class Meta:
        model = Contract
        fields = [
            "id", "employee", "employee_name", "employee_code", "position",
            "contract_type", "start_date", "end_date", "status", "days_remaining",
            "document", "terms", "created_at", "updated_at"
        ]
        read_only_fields = ["id", "created_at", "updated_at"]

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

    def get_days_remaining(self, obj):
        if not obj.end_date:
            return None
        from datetime import date as _date
        return (obj.end_date - _date.today()).days


class CommunicationSerializer(serializers.ModelSerializer):
    """Serializer for Communication model."""
    author_name = serializers.SerializerMethodField()

    class Meta:
        model = Communication
        fields = ["id", "title", "content", "communication_type", "author", "author_name", "is_pinned", "attachments", "created_at"]
        read_only_fields = ["id", "author", "created_at"]

    def get_author_name(self, obj):
        return obj.author.get_full_name() or obj.author.username


class SurveySerializer(serializers.ModelSerializer):
    """Serializer for Survey model."""
    created_by_name = serializers.SerializerMethodField()
    response_count = serializers.SerializerMethodField()
    is_open = serializers.BooleanField(read_only=True)
    has_responded = serializers.SerializerMethodField()

    class Meta:
        model = Survey
        fields = ["id", "title", "description", "questions", "status",
                  "open_at", "close_at", "is_open", "response_count", "has_responded",
                  "created_by", "created_by_name", "created_at", "updated_at"]
        read_only_fields = ["id", "created_by", "created_at", "updated_at"]

    def get_created_by_name(self, obj):
        return obj.created_by.get_full_name() or obj.created_by.username

    def get_response_count(self, obj) -> int:
        cnt = getattr(obj, "response_count_anno", None)
        return cnt if cnt is not None else obj.response_set.count()

    def get_has_responded(self, obj) -> bool:
        # Annotated on SurveyViewSet.get_queryset(); fallback covers paths that
        # serialize a raw Survey (e.g. the `fill` action).
        anno = getattr(obj, "has_responded_anno", None)
        if anno is not None:
            return anno
        request = self.context.get("request")
        if not request or not request.user.is_authenticated:
            return False
        return obj.response_set.filter(respondent=request.user).exists()


class SurveyResponseSerializer(serializers.ModelSerializer):
    """A respondent's survey submission."""
    respondent_name = serializers.SerializerMethodField()

    class Meta:
        model = SurveyResponse
        fields = ["id", "survey", "respondent", "respondent_name", "answers", "created_at"]
        read_only_fields = ["id", "survey", "respondent", "created_at"]

    def get_respondent_name(self, obj):
        return obj.respondent.get_full_name() or obj.respondent.username


class FamilySerializer(serializers.ModelSerializer):
    """Serializer for Family model."""
    employee_name = serializers.SerializerMethodField()

    class Meta:
        model = Family
        fields = [
            "id", "employee", "employee_name", "name", "relationship",
            "date_of_birth", "phone", "email", "occupation", "notes",
            "created_at", "updated_at"
        ]
        read_only_fields = ["id", "created_at", "updated_at"]

    def get_employee_name(self, obj):
        return str(obj.employee)