"""HR field-level and object-level authorization (BE-001/BE-003/BE-004).

Scope note: this module previously also contained tests for a department-
transfer approval workflow, a pending-employee onboarding flow, and an
account-activation endpoint. None of those are implemented against the
current domain model — `Employee.primary_department` does not exist,
`/api/auth/activate/` and `/api/rbac/access-requests/` are not routed, and
`User` has no `ORGANIZATION_ROLE_DIRECTOR`/`ACCOUNT_PENDING` class constants
(only plain string choices). Per the task's non-negotiable rule to stop and
document rather than invent scope, those tests were removed rather than
rewritten against invented behavior. They tested functionality unrelated to
HR field-level confidentiality (BE-001/003/004), which is this module's
actual scope. If that workflow is intended, it needs its own implementation
task with its own audit.
"""
import pytest
from django.contrib.auth import get_user_model
from rest_framework.test import APIClient

from apps.companies.models import Department, Organization, OrganizationMembership
from apps.core.models import AppConfig, DepartmentMembership
from apps.hr.models import Employee
from apps.hr.serializers import EmployeeDirectorySerializer

User = get_user_model()

CONFIDENTIAL_FIELDS = {
    "salary", "address", "emergency_contact_name", "emergency_contact_phone",
    "notes", "payroll_overrides", "user_phone", "hire_date", "termination_date",
    "manager", "contract_end_date", "pending_documents_count", "temp_password",
}


def client_for(user):
    client = APIClient()
    client.force_authenticate(user)
    return client


@pytest.fixture
def organization(db):
    return Organization.objects.create(name="Test Org")


def _make_employee(organization, email, *, salary="123456.00", address="Private home"):
    from apps.hr.signals import ensure_employee_stub

    user = User.objects.create_user(email=email, password="p")
    OrganizationMembership.objects.create(organization=organization, user=user)
    ensure_employee_stub(user)
    employee = Employee.objects.get(user=user)
    employee.employee_id = email.split("@")[0].upper()
    employee.position = "Analyst"
    employee.department = "Ops"
    employee.hire_date = "2026-01-01"
    employee.salary = salary
    employee.address = address
    employee.emergency_contact_name = "Contact Name"
    employee.emergency_contact_phone = "+1234567890"
    employee.notes = "Confidential note"
    employee.save()
    return user, employee


@pytest.mark.django_db
def test_directory_list_never_exposes_confidential_fields():
    org = Organization.objects.create(name="Test Org")
    viewer, _ = _make_employee(org, "viewer@rbac.test")
    _, target = _make_employee(org, "target@rbac.test", salary="999999.00", address="Secret address")

    resp = client_for(viewer).get("/api/employees/")
    assert resp.status_code == 200
    rows = resp.data["results"] if isinstance(resp.data, dict) and "results" in resp.data else resp.data
    assert len(rows) >= 2
    for row in rows:
        leaked = CONFIDENTIAL_FIELDS & set(row.keys())
        assert not leaked, f"directory row leaked confidential fields: {sorted(leaked)}"


def test_hr_authority_capability_imports_cleanly():
    """BE-004: the HR authorization seam must import without referencing a
    nonexistent helper or model field. `apps.hr.permissions` (the old dead
    module with EmployeeAccessPermission) was deleted — it was never wired
    into any view and referenced a nonexistent `primary_department_id`
    field. The live seam is `apps.core.permissions.user_is_hr_authority`,
    used directly by `EmployeeViewSet.retrieve`."""
    from apps.core.permissions import user_is_hr_authority  # noqa: F401


@pytest.mark.django_db
def test_directory_serializer_class_has_no_confidential_fields():
    """Direct check mirroring bug.md's fast signal — asserts on the field
    declaration itself, not just a response body, so a future edit that
    widens the allowlist fails immediately."""
    leaked = CONFIDENTIAL_FIELDS & set(EmployeeDirectorySerializer().fields)
    assert not leaked, f"EmployeeDirectorySerializer declares confidential fields: {sorted(leaked)}"


@pytest.mark.django_db
def test_employee_sees_own_full_detail(organization):
    user, employee = _make_employee(organization, "self@rbac.test")
    resp = client_for(user).get(f"/api/employees/{employee.id}/")
    assert resp.status_code == 200
    assert resp.data["salary"] == "123456.00"
    assert resp.data["address"] == "Private home"


@pytest.mark.django_db
def test_ordinary_staff_gets_redacted_detail_for_other_employee(organization):
    viewer, _ = _make_employee(organization, "viewer2@rbac.test")
    _, target = _make_employee(organization, "target2@rbac.test")

    resp = client_for(viewer).get(f"/api/employees/{target.id}/")
    assert resp.status_code == 200
    leaked = CONFIDENTIAL_FIELDS & set(resp.data.keys())
    assert not leaked, f"redacted detail leaked confidential fields: {sorted(leaked)}"


@pytest.mark.django_db
def test_manager_without_hr_authority_gets_redacted_detail(organization):
    hr_department = Department.objects.create(organization=organization, name="People Operations")
    other_department = Department.objects.create(organization=organization, name="Engineering")
    AppConfig.objects.create(hr_department=hr_department)

    manager, _ = _make_employee(organization, "manager@rbac.test")
    DepartmentMembership.objects.create(user=manager, department=other_department, role="manager")
    _, target = _make_employee(organization, "report@rbac.test")

    resp = client_for(manager).get(f"/api/employees/{target.id}/")
    assert resp.status_code == 200
    leaked = CONFIDENTIAL_FIELDS & set(resp.data.keys())
    assert not leaked, f"manager without HR authority saw confidential fields: {sorted(leaked)}"


@pytest.mark.django_db
def test_hr_authority_member_sees_full_detail(organization):
    hr_department = Department.objects.create(organization=organization, name="People Operations")
    AppConfig.objects.create(hr_department=hr_department)

    hr_staff, _ = _make_employee(organization, "hrstaff@rbac.test")
    DepartmentMembership.objects.create(user=hr_staff, department=hr_department, role="manager")
    _, target = _make_employee(organization, "target3@rbac.test", salary="555.00")

    resp = client_for(hr_staff).get(f"/api/employees/{target.id}/")
    assert resp.status_code == 200
    assert resp.data["salary"] == "555.00"


@pytest.mark.django_db
def test_director_sees_full_detail_without_hr_department_membership(organization):
    """Director is a fixed organization-authority tier (CONTEXT.md), distinct
    from Superadmin/is_superuser — it grants HR read with no department setup."""
    director, _ = _make_employee(organization, "director@rbac.test")
    director.organization_role = "director"
    director.save(update_fields=["organization_role"])
    _, target = _make_employee(organization, "target4@rbac.test", salary="777.00")

    resp = client_for(director).get(f"/api/employees/{target.id}/")
    assert resp.status_code == 200
    assert resp.data["salary"] == "777.00"


@pytest.mark.django_db
def test_superuser_without_hr_authority_gets_redacted_detail(organization):
    """Invariant #9: platform superuser status alone must not grant
    confidential HR read access when the HR policy does not grant it."""
    from apps.companies.models import OrganizationMembership as Membership

    superuser = User.objects.create_superuser(email="root@rbac.test", password="p")
    # create_superuser auto-provisions its own Organization + membership;
    # reassign it to the shared test org so get_active_organization resolves
    # there instead of the auto-created one (single-membership assumption).
    auto_org = superuser.organizations.get()
    Membership.objects.filter(organization=auto_org, user=superuser).update(organization=organization)
    auto_org.delete()
    _, target = _make_employee(organization, "target5@rbac.test", salary="888.00")

    resp = client_for(superuser).get(f"/api/employees/{target.id}/")
    assert resp.status_code == 200
    leaked = CONFIDENTIAL_FIELDS & set(resp.data.keys())
    assert not leaked, f"superuser without HR authority saw confidential fields: {sorted(leaked)}"


@pytest.mark.django_db
def test_other_organization_employee_is_not_visible(organization):
    """Cross-organization records must not disclose existence — 404, not a
    redacted 200, since the record is outside the caller's tenant entirely."""
    viewer, _ = _make_employee(organization, "viewer3@rbac.test")
    other_org = Organization.objects.create(name="Other Org")
    _, other_employee = _make_employee(other_org, "outsider@rbac.test")

    resp = client_for(viewer).get(f"/api/employees/{other_employee.id}/")
    assert resp.status_code == 404
