"""Personnel rules. Scope of personnel.view: `self` = my own record, `organization`+ = everyone.
Editing profiles and all contract access need organization scope (salary is sensitive)."""
from __future__ import annotations

from rest_framework.exceptions import NotFound, ValidationError

from apps.audit import services as audit
from apps.organizations.models import OrganizationMembership
from apps.personnel.models import Contract, PersonnelProfile
from domain.access_policy import scope_rank

PROFILE_FIELDS = ("job_title", "department", "employment_type", "hire_date", "end_date", "phone", "emergency_contact", "notes")
CONTRACT_FIELDS = ("title", "start_date", "end_date", "pay_type", "pay_amount", "status", "notes")


def visible_memberships(ctx):
    qs = OrganizationMembership.objects.filter(organization=ctx.organization).select_related("user", "personnel_profile")
    scope = ctx.effective.scope_of("personnel.view")
    if scope is not None and scope_rank(scope) >= scope_rank("organization"):
        return qs
    return qs.filter(user=ctx.user)


def get_membership(ctx, pk: int) -> OrganizationMembership:
    membership = visible_memberships(ctx).filter(pk=pk).first()
    if membership is None:
        raise NotFound()
    return membership


def _org_member(ctx, pk: int) -> OrganizationMembership:
    membership = OrganizationMembership.objects.filter(organization=ctx.organization, pk=pk).first()
    if membership is None:
        raise NotFound()
    return membership


def remove_from_personnel(ctx, pk: int, *, request=None) -> None:
    """End the membership so the person leaves the personnel list; the user account, attendance
    history, contracts and payroll records are kept."""
    from apps.organizations import services as org_services

    membership = _org_member(ctx, pk)
    org_services.set_member_status(
        actor=ctx, membership=membership, status=OrganizationMembership.Status.LEFT, request=request
    )


def _validate(data: dict) -> None:
    if "employment_type" in data and data["employment_type"] not in dict(PersonnelProfile.EMPLOYMENT_TYPES):
        raise ValidationError({"employment_type": "Invalid employment type."})
    if data.get("hire_date") and data.get("end_date") and data["end_date"] < data["hire_date"]:
        raise ValidationError({"end_date": "End date is before hire date."})


def update_profile(ctx, pk: int, *, data: dict, request=None) -> PersonnelProfile:
    membership = _org_member(ctx, pk)
    _validate(data)
    profile, _ = PersonnelProfile.objects.get_or_create(membership=membership, defaults={"organization": ctx.organization})
    for field in PROFILE_FIELDS:
        if field in data:
            setattr(profile, field, data[field])
    profile.save()
    audit.record(
        actor=ctx.user, action="personnel.update", organization=ctx.organization,
        entity_type="membership", entity_id=membership.pk, after={k: str(data[k]) for k in data if k != "notes"},
        request=request,
    )
    return profile


def list_contracts(ctx, pk: int):
    return Contract.objects.filter(organization=ctx.organization, membership=_org_member(ctx, pk))


def get_contract(ctx, pk: int) -> Contract:
    contract = Contract.objects.filter(organization=ctx.organization, pk=pk).first()
    if contract is None:
        raise NotFound()
    return contract


def _validate_contract(data: dict) -> None:
    if "title" in data and not str(data["title"]).strip():
        raise ValidationError({"title": "Title is required."})
    if "status" in data and data["status"] not in dict(Contract.STATUSES):
        raise ValidationError({"status": "Invalid status."})
    if "pay_type" in data and data["pay_type"] not in dict(Contract.PAY_TYPES):
        raise ValidationError({"pay_type": "Invalid pay type."})
    if data.get("pay_amount") is not None and data["pay_amount"] < 0:
        raise ValidationError({"pay_amount": "Salary cannot be negative."})
    if data.get("start_date") and data.get("end_date") and data["end_date"] < data["start_date"]:
        raise ValidationError({"end_date": "End date is before start date."})


def create_contract(ctx, pk: int, *, data: dict, request=None) -> Contract:
    membership = _org_member(ctx, pk)
    _validate_contract({**data, "title": data.get("title", "")})
    contract = Contract.objects.create(organization=ctx.organization, membership=membership, **{k: data[k] for k in CONTRACT_FIELDS if k in data})
    audit.record(actor=ctx.user, action="contract.create", organization=ctx.organization, entity_type="contract", entity_id=contract.pk, request=request)
    return contract


def update_contract(ctx, contract: Contract, *, data: dict, request=None) -> Contract:
    _validate_contract({**{"start_date": contract.start_date, "end_date": contract.end_date}, **data})
    for field in CONTRACT_FIELDS:
        if field in data:
            setattr(contract, field, data[field])
    contract.save()
    audit.record(actor=ctx.user, action="contract.update", organization=ctx.organization, entity_type="contract", entity_id=contract.pk, request=request)
    return contract


def delete_contract(ctx, contract: Contract, *, request=None) -> None:
    pk = contract.pk
    contract.delete()
    audit.record(actor=ctx.user, action="contract.delete", organization=ctx.organization, entity_type="contract", entity_id=pk, request=request)
