"""Tests für agent-03-abwesenheit"""
import pytest
import pytest_asyncio
from datetime import date, timedelta
from httpx import AsyncClient


# ── Fixtures ──────────────────────────────────────────────────────────────────

@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def abs_company(client: AsyncClient):
    """Eigene Company für Absence-Tests."""
    resp = await client.post("/api/v1/auth/register", json={
        "company_name": "Absence AG",
        "first_name": "Admin",
        "last_name": "Test",
        "email": "admin@absenceag.de",
        "password": "Secret123",
    })
    assert resp.status_code == 201
    tokens = resp.json()
    me = await client.get(
        "/api/v1/auth/me",
        headers={"Authorization": f"Bearer {tokens['access_token']}"},
    )
    return {"tokens": tokens, "user": me.json()}


@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def abs_headers(abs_company):
    return {"Authorization": f"Bearer {abs_company['tokens']['access_token']}"}


@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def abs_approver_headers(client: AsyncClient, abs_headers):
    """Zweiter Admin in Absence AG – kann Abwesenheiten anderer genehmigen."""
    resp = await client.post("/api/v1/users/invite", json={
        "first_name": "Approver",
        "last_name": "Admin",
        "email": "approver@absenceag.de",
        "role": "COMPANY_ADMIN",
        "initial_password": "Secret123",
    }, headers=abs_headers)
    assert resp.status_code == 201, resp.text
    login = await client.post("/api/v1/auth/login", json={
        "email": "approver@absenceag.de",
        "password": "Secret123",
    })
    assert login.status_code == 200, login.text
    return {"Authorization": f"Bearer {login.json()['access_token']}"}


@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def vacation_type_id(client: AsyncClient, abs_headers):
    """Urlaubs-Typ der Company holen."""
    resp = await client.get("/api/v1/absence-types/", headers=abs_headers)
    assert resp.status_code == 200
    types = resp.json()
    assert len(types) > 0, "Default-Abwesenheitstypen fehlen"
    vacation = next((t for t in types if t["name"] == "Urlaub"), types[0])
    return vacation["id"]


@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def sick_type_id(client: AsyncClient, abs_headers):
    resp = await client.get("/api/v1/absence-types/", headers=abs_headers)
    types = resp.json()
    sick = next((t for t in types if t["name"] == "Krankheit"), types[0])
    return sick["id"]


# ── Default Types ──────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_default_absence_types_created_on_register(client: AsyncClient, abs_headers):
    """Nach der Registrierung müssen Default-Typen vorhanden sein."""
    resp = await client.get("/api/v1/absence-types/", headers=abs_headers)
    assert resp.status_code == 200
    types = resp.json()
    names = {t["name"] for t in types}
    assert "Urlaub" in names
    assert "Krankheit" in names
    assert "Homeoffice" in names
    assert "Dienstreise" in names
    assert "Sonderurlaub" in names


# ── Absence Types CRUD ─────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_create_absence_type(client: AsyncClient, abs_headers):
    resp = await client.post(
        "/api/v1/absence-types/",
        json={
            "name": "Elternzeit",
            "color": "#F59E0B",
            "requires_approval": True,
            "deducts_vacation": False,
            "is_paid": False,
        },
        headers=abs_headers,
    )
    assert resp.status_code == 201
    assert resp.json()["name"] == "Elternzeit"


@pytest.mark.asyncio
async def test_update_absence_type(client: AsyncClient, abs_headers, vacation_type_id):
    resp = await client.patch(
        f"/api/v1/absence-types/{vacation_type_id}",
        json={"max_days_per_year": 30},
        headers=abs_headers,
    )
    assert resp.status_code == 200
    assert resp.json()["max_days_per_year"] == 30


# ── Absences ───────────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_create_absence_vacation(client: AsyncClient, abs_headers, vacation_type_id):
    next_monday = date.today() + timedelta(days=(7 - date.today().weekday()))
    resp = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(vacation_type_id),
            "start_date": str(next_monday),
            "end_date": str(next_monday + timedelta(days=4)),
        },
        headers=abs_headers,
    )
    assert resp.status_code == 201
    data = resp.json()
    assert data["status"] == "pending"
    assert data["working_days"] > 0


@pytest.mark.asyncio
async def test_create_absence_sick_auto_approved(client: AsyncClient, abs_headers, sick_type_id):
    """Krankheit hat requires_approval=False → sofort approved."""
    resp = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(sick_type_id),
            "start_date": str(date.today()),
            "end_date": str(date.today()),
        },
        headers=abs_headers,
    )
    assert resp.status_code == 201
    # Krankheit erfordert keine Genehmigung → approved
    data = resp.json()
    assert data["status"] == "approved"


@pytest.mark.asyncio
async def test_list_absences(client: AsyncClient, abs_headers):
    resp = await client.get("/api/v1/absences/", headers=abs_headers)
    assert resp.status_code == 200
    data = resp.json()
    assert "total" in data
    assert "items" in data


@pytest.mark.asyncio
async def test_get_absence_by_id(client: AsyncClient, abs_headers, vacation_type_id):
    next_monday = date.today() + timedelta(days=(7 - date.today().weekday()) + 7)
    create_resp = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(vacation_type_id),
            "start_date": str(next_monday),
            "end_date": str(next_monday + timedelta(days=2)),
        },
        headers=abs_headers,
    )
    absence_id = create_resp.json()["id"]

    resp = await client.get(f"/api/v1/absences/{absence_id}", headers=abs_headers)
    assert resp.status_code == 200
    assert resp.json()["id"] == absence_id


@pytest.mark.asyncio
async def test_approve_absence(client: AsyncClient, abs_headers, abs_approver_headers, vacation_type_id):
    next_monday = date.today() + timedelta(days=(7 - date.today().weekday()) + 14)
    create_resp = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(vacation_type_id),
            "start_date": str(next_monday),
            "end_date": str(next_monday + timedelta(days=4)),
        },
        headers=abs_headers,
    )
    absence_id = create_resp.json()["id"]

    resp = await client.post(f"/api/v1/absences/{absence_id}/approve", headers=abs_approver_headers)
    assert resp.status_code == 200
    assert resp.json()["status"] == "approved"


@pytest.mark.asyncio
async def test_reject_absence(client: AsyncClient, abs_headers, vacation_type_id):
    next_monday = date.today() + timedelta(days=(7 - date.today().weekday()) + 21)
    create_resp = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(vacation_type_id),
            "start_date": str(next_monday),
            "end_date": str(next_monday + timedelta(days=4)),
        },
        headers=abs_headers,
    )
    absence_id = create_resp.json()["id"]

    resp = await client.post(
        f"/api/v1/absences/{absence_id}/reject",
        json={"rejection_reason": "Urlaubssperre in diesem Zeitraum"},
        headers=abs_headers,
    )
    assert resp.status_code == 200
    assert resp.json()["status"] == "rejected"
    assert resp.json()["rejection_reason"] == "Urlaubssperre in diesem Zeitraum"


@pytest.mark.asyncio
async def test_cancel_absence(client: AsyncClient, abs_headers, vacation_type_id):
    """Eigenen ausstehenden Antrag stornieren."""
    next_monday = date.today() + timedelta(days=(7 - date.today().weekday()) + 28)
    create_resp = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(vacation_type_id),
            "start_date": str(next_monday),
            "end_date": str(next_monday + timedelta(days=4)),
        },
        headers=abs_headers,
    )
    absence_id = create_resp.json()["id"]

    resp = await client.delete(f"/api/v1/absences/{absence_id}", headers=abs_headers)
    assert resp.status_code == 200
    assert resp.json()["status"] == "cancelled"


@pytest.mark.asyncio
async def test_calendar(client: AsyncClient, abs_headers):
    resp = await client.get(
        "/api/v1/absences/calendar",
        params={"year": 2026, "month": 4},
        headers=abs_headers,
    )
    assert resp.status_code == 200
    assert isinstance(resp.json(), list)


# ── Vacation Balance ───────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_own_balance(client: AsyncClient, abs_headers):
    resp = await client.get("/api/v1/absences/balance", params={"year": 2026}, headers=abs_headers)
    assert resp.status_code == 200
    data = resp.json()
    assert "entitled_days" in data
    assert "remaining_days" in data
    assert data["entitled_days"] == 30


@pytest.mark.asyncio
async def test_balance_deducted_after_approve(client: AsyncClient, abs_headers, abs_approver_headers, vacation_type_id, abs_company):
    """Urlaubskonto wird nach Genehmigung abgezogen."""
    user_id = abs_company["user"]["id"]

    # Initiales Konto
    before = await client.get("/api/v1/absences/balance", params={"year": 2026}, headers=abs_headers)
    used_before = before.json()["used_days"]

    # Urlaub beantragen (Mo-Fr = 5 Tage)
    next_monday = date.today() + timedelta(days=(7 - date.today().weekday()) + 35)
    create_resp = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(vacation_type_id),
            "start_date": str(next_monday),
            "end_date": str(next_monday + timedelta(days=4)),
        },
        headers=abs_headers,
    )
    absence_id = create_resp.json()["id"]
    working_days = create_resp.json()["working_days"]

    # Genehmigen (zweiter Admin, nicht self-approval)
    approve_resp = await client.post(f"/api/v1/absences/{absence_id}/approve", headers=abs_approver_headers)
    assert approve_resp.status_code == 200, approve_resp.text

    # Konto prüfen
    after = await client.get("/api/v1/absences/balance", params={"year": 2026}, headers=abs_headers)
    assert after.json()["used_days"] == used_before + working_days


# ── Public Holidays ────────────────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_create_and_list_public_holiday(client: AsyncClient, abs_headers):
    resp = await client.post(
        "/api/v1/public-holidays/",
        json={
            "country": "DE",
            "state": None,
            "date": "2026-01-01",
            "name": "Neujahr",
        },
        headers=abs_headers,
    )
    assert resp.status_code == 201
    assert resp.json()["name"] == "Neujahr"

    list_resp = await client.get(
        "/api/v1/public-holidays/",
        params={"year": 2026, "country": "DE"},
        headers=abs_headers,
    )
    assert list_resp.status_code == 200
    holidays = list_resp.json()
    assert any(h["name"] == "Neujahr" for h in holidays)


# ── Working Days Calculation ───────────────────────────────────────────────────

def test_calc_working_days_full_week():
    from app.services.absence_service import AbsenceService
    svc = AbsenceService()
    # Mo-Fr
    days = svc._calc_working_days(
        date(2026, 3, 30), date(2026, 4, 3), set(), False, False
    )
    assert days == 5.0


def test_calc_working_days_with_holiday():
    from app.services.absence_service import AbsenceService
    svc = AbsenceService()
    # Mo-Fr mit Feiertag am Mittwoch
    days = svc._calc_working_days(
        date(2026, 3, 30), date(2026, 4, 3),
        {date(2026, 4, 1)},  # Mittwoch = Feiertag
        False, False
    )
    assert days == 4.0


def test_calc_working_days_half_day():
    from app.services.absence_service import AbsenceService
    svc = AbsenceService()
    # Mo-Fr, halber Montag
    days = svc._calc_working_days(
        date(2026, 3, 30), date(2026, 4, 3), set(), True, False
    )
    assert days == 4.5


def test_calc_working_days_weekend_only():
    from app.services.absence_service import AbsenceService
    svc = AbsenceService()
    days = svc._calc_working_days(
        date(2026, 3, 28), date(2026, 3, 29), set(), False, False
    )
    assert days == 0.0


# ── Krankmeldung (agent-05) ───────────────────────────────────────────────────

@pytest.mark.asyncio
async def test_quick_sick_creates_approved_absence(client: AsyncClient, abs_headers):
    """POST /absences/quick-sick → sofort approved, ohne Typ-Auswahl."""
    today = date.today()
    resp = await client.post(
        "/api/v1/absences/quick-sick",
        json={"start_date": str(today), "end_date": str(today)},
        headers=abs_headers,
    )
    assert resp.status_code == 201
    data = resp.json()
    assert data["status"] == "approved"
    assert data["certificate_required_by"] is not None


@pytest.mark.asyncio
async def test_certificate_required_by_uses_type_threshold(
    client: AsyncClient, abs_headers, sick_type_id
):
    """certificate_required_by = start_date + AbsenceType.certificate_after_days (default 3)."""
    start = date(2027, 6, 1)
    resp = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(sick_type_id),
            "start_date": str(start),
            "end_date": str(start),
        },
        headers=abs_headers,
    )
    assert resp.status_code == 201
    data = resp.json()
    expected = start + timedelta(days=3)
    assert data["certificate_required_by"] == str(expected)


@pytest.mark.asyncio
async def test_certificate_required_by_respects_type_override(
    client: AsyncClient, abs_headers, sick_type_id
):
    """PATCH AbsenceType.certificate_after_days = 7 → neue Absences nutzen 7."""
    upd = await client.patch(
        f"/api/v1/absence-types/{sick_type_id}",
        json={"certificate_after_days": 7},
        headers=abs_headers,
    )
    assert upd.status_code == 200
    assert upd.json()["certificate_after_days"] == 7

    start = date(2027, 7, 5)
    resp = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(sick_type_id),
            "start_date": str(start),
            "end_date": str(start),
        },
        headers=abs_headers,
    )
    assert resp.status_code == 201
    expected = start + timedelta(days=7)
    assert resp.json()["certificate_required_by"] == str(expected)

    # Reset auf default für nachfolgende Tests
    await client.patch(
        f"/api/v1/absence-types/{sick_type_id}",
        json={"certificate_after_days": 3},
        headers=abs_headers,
    )


@pytest.mark.asyncio
async def test_mark_certificate_received(client: AsyncClient, abs_headers, sick_type_id):
    """HR/Admin markiert Attest-Eingang per PATCH /absences/{id}/certificate."""
    start = date(2027, 8, 10)
    create = await client.post(
        "/api/v1/absences/",
        json={
            "type_id": str(sick_type_id),
            "start_date": str(start),
            "end_date": str(start),
        },
        headers=abs_headers,
    )
    absence_id = create.json()["id"]
    assert create.json()["certificate_received_at"] is None

    resp = await client.patch(
        f"/api/v1/absences/{absence_id}/certificate",
        json={},  # default = today
        headers=abs_headers,
    )
    assert resp.status_code == 200
    assert resp.json()["certificate_received_at"] == str(date.today())


@pytest.mark.asyncio
async def test_sick_stats_bradford_factor(client: AsyncClient, abs_headers):
    """GET /absences/sick-stats: Bradford = episodes² × total_days, rolling 12 Monate."""
    resp = await client.get("/api/v1/absences/sick-stats", headers=abs_headers)
    assert resp.status_code == 200
    stats = resp.json()
    # Vorherige Tests haben mindestens eine Krankmeldung im 12-Monats-Fenster erzeugt
    assert len(stats) >= 1
    row = stats[0]
    assert row["episodes"] >= 1
    assert row["total_days"] >= 0.0
    # Bradford-Formel verifizieren
    expected = float(row["episodes"]) ** 2 * row["total_days"]
    assert abs(row["bradford_factor"] - expected) < 0.001


# ── agent-11 PR1: Vertretung · Stornierung · Kommentare ────────────────────────

@pytest_asyncio.fixture(scope="session", loop_scope="session")
async def substitute_user_id(client: AsyncClient, abs_headers):
    """Mitarbeiter, der als Vertretung eingetragen werden kann."""
    resp = await client.post("/api/v1/users/invite", json={
        "first_name": "Sub", "last_name": "Stitute",
        "email": "sub@absenceag.de", "role": "EMPLOYEE",
        "initial_password": "Secret123",
    }, headers=abs_headers)
    assert resp.status_code == 201, resp.text
    return resp.json()["id"]


def _future_monday(weeks: int) -> date:
    return date.today() + timedelta(days=(7 - date.today().weekday()) + 7 * weeks)


@pytest.mark.asyncio
async def test_cancellation_request_flow(
    client: AsyncClient, abs_headers, abs_approver_headers, vacation_type_id
):
    """Genehmigten Antrag → Stornoantrag → Manager genehmigt → cancelled + Urlaub zurück."""
    start = _future_monday(7)
    create = await client.post("/api/v1/absences/", json={
        "type_id": str(vacation_type_id),
        "start_date": str(start), "end_date": str(start + timedelta(days=4)),
    }, headers=abs_headers)
    aid = create.json()["id"]
    working_days = create.json()["working_days"]

    approve = await client.post(f"/api/v1/absences/{aid}/approve", headers=abs_approver_headers)
    assert approve.status_code == 200
    used_after_approve = (await client.get(
        "/api/v1/absences/balance", params={"year": start.year}, headers=abs_headers
    )).json()["used_days"]

    # Stornoantrag durch Mitarbeiter (Owner)
    req = await client.post(
        f"/api/v1/absences/{aid}/request-cancellation",
        json={"reason": "Plan geaendert"}, headers=abs_headers,
    )
    assert req.status_code == 200, req.text
    assert req.json()["status"] == "cancellation_requested"

    # Manager genehmigt die Stornierung
    ok = await client.post(f"/api/v1/absences/{aid}/approve", headers=abs_approver_headers)
    assert ok.status_code == 200, ok.text
    assert ok.json()["status"] == "cancelled"

    used_after_cancel = (await client.get(
        "/api/v1/absences/balance", params={"year": start.year}, headers=abs_headers
    )).json()["used_days"]
    assert used_after_cancel == used_after_approve - int(working_days)


@pytest.mark.asyncio
async def test_cancellation_request_rejected_keeps_approved(
    client: AsyncClient, abs_headers, abs_approver_headers, vacation_type_id
):
    start = _future_monday(9)
    create = await client.post("/api/v1/absences/", json={
        "type_id": str(vacation_type_id),
        "start_date": str(start), "end_date": str(start + timedelta(days=2)),
    }, headers=abs_headers)
    aid = create.json()["id"]
    await client.post(f"/api/v1/absences/{aid}/approve", headers=abs_approver_headers)
    await client.post(f"/api/v1/absences/{aid}/request-cancellation", json={}, headers=abs_headers)

    rej = await client.post(
        f"/api/v1/absences/{aid}/reject",
        json={"rejection_reason": "Vertretung fehlt"}, headers=abs_approver_headers,
    )
    assert rej.status_code == 200, rej.text
    assert rej.json()["status"] == "approved"


@pytest.mark.asyncio
async def test_request_cancellation_requires_approved(
    client: AsyncClient, abs_headers, vacation_type_id
):
    """PENDING-Antrag kann nicht zur Stornierung eingereicht werden (nur direkt löschen)."""
    start = _future_monday(11)
    create = await client.post("/api/v1/absences/", json={
        "type_id": str(vacation_type_id),
        "start_date": str(start), "end_date": str(start + timedelta(days=1)),
    }, headers=abs_headers)
    aid = create.json()["id"]
    req = await client.post(f"/api/v1/absences/{aid}/request-cancellation", json={}, headers=abs_headers)
    assert req.status_code == 409


@pytest.mark.asyncio
async def test_substitute_filter_and_notification(
    client: AsyncClient, abs_headers, abs_approver_headers, vacation_type_id, substitute_user_id
):
    """Antrag mit Vertretung → Vertreter sieht ihn unter ?as_substitute=true."""
    start = _future_monday(13)
    create = await client.post("/api/v1/absences/", json={
        "type_id": str(vacation_type_id),
        "start_date": str(start), "end_date": str(start + timedelta(days=2)),
        "substitute_id": substitute_user_id,
    }, headers=abs_headers)
    assert create.status_code == 201, create.text
    aid = create.json()["id"]
    assert create.json()["substitute_id"] == substitute_user_id
    await client.post(f"/api/v1/absences/{aid}/approve", headers=abs_approver_headers)

    sub_login = await client.post("/api/v1/auth/login", json={
        "email": "sub@absenceag.de", "password": "Secret123",
    })
    sub_headers = {"Authorization": f"Bearer {sub_login.json()['access_token']}"}
    lst = await client.get("/api/v1/absences/?as_substitute=true", headers=sub_headers)
    assert lst.status_code == 200, lst.text
    assert any(a["id"] == aid for a in lst.json()["items"])


@pytest.mark.asyncio
async def test_absence_comments(
    client: AsyncClient, abs_headers, vacation_type_id
):
    """Kommentar posten + System-Kommentar bei Stornoantrag erscheint im Thread."""
    start = _future_monday(15)
    create = await client.post("/api/v1/absences/", json={
        "type_id": str(vacation_type_id),
        "start_date": str(start), "end_date": str(start + timedelta(days=1)),
        "note": "Brueckentag",
    }, headers=abs_headers)
    aid = create.json()["id"]

    add = await client.post(
        f"/api/v1/absences/{aid}/comments",
        json={"body": "Bitte zuegig pruefen"}, headers=abs_headers,
    )
    assert add.status_code == 201, add.text
    assert add.json()["is_system"] is False
    assert add.json()["author_name"]

    lst = await client.get(f"/api/v1/absences/{aid}/comments", headers=abs_headers)
    assert lst.status_code == 200
    bodies = [c["body"] for c in lst.json()]
    assert "Bitte zuegig pruefen" in bodies


# ── agent-11 PR2: Urlaubsanspruch (Verfall · Pro-rata · Teilzeit) ──────────────

def test_effective_available_not_expired():
    import uuid as _u
    from app.services.absence_service import AbsenceService
    from app.models.vacation_balance import VacationBalance
    b = VacationBalance(user_id=_u.uuid4(), year=2026, entitled_days=30, special_days=0, carried_over=5, used_days=3)
    assert AbsenceService.effective_available(b, False) == 32   # 30 + 5 - 3
    assert AbsenceService.effective_available(b, True) == 30    # Resturlaub verfällt (3 < 5)


def test_effective_available_expired_overdraw():
    import uuid as _u
    from app.services.absence_service import AbsenceService
    from app.models.vacation_balance import VacationBalance
    b = VacationBalance(user_id=_u.uuid4(), year=2026, entitled_days=30, special_days=0, carried_over=5, used_days=8)
    assert AbsenceService.effective_available(b, True) == 27    # 30 - max(0, 8 - 5)


@pytest.mark.asyncio
async def test_company_vacation_settings_roundtrip(client: AsyncClient, abs_headers):
    r = await client.patch("/api/v1/companies/me", json={
        "vacation_default_days": 28, "vacation_prorate_first_year": True, "vacation_from_schedule": False,
    }, headers=abs_headers)
    assert r.status_code == 200, r.text
    assert r.json()["vacation_default_days"] == 28
    await client.patch("/api/v1/companies/me", json={"vacation_default_days": 30}, headers=abs_headers)


@pytest.mark.asyncio
async def test_prorate_entry_year(client: AsyncClient, abs_headers):
    inv = await client.post("/api/v1/users/invite", json={
        "first_name": "Pro", "last_name": "Rata", "email": "prorata@absenceag.de",
        "role": "EMPLOYEE", "initial_password": "Secret123",
    }, headers=abs_headers)
    uid = inv.json()["id"]
    yr = date.today().year
    await client.patch(f"/api/v1/users/{uid}", json={"entry_date": f"{yr}-07-01"}, headers=abs_headers)
    bal = await client.get(f"/api/v1/absences/balance/{uid}?year={yr}", headers=abs_headers)
    assert bal.status_code == 200, bal.text
    assert bal.json()["entitled_days"] == 15   # 30 * 6/12 (Eintritt Juli)


@pytest.mark.asyncio
async def test_part_time_entitlement_from_schedule(client: AsyncClient, abs_headers):
    sched = await client.post("/api/v1/time/schedules", json={
        "name": "Teilzeit 3 Tage", "mon_h": 8, "tue_h": 8, "wed_h": 8,
        "thu_h": 0, "fri_h": 0, "valid_from": "2026-01-01",
    }, headers=abs_headers)
    assert sched.status_code == 201, sched.text
    sid = sched.json()["id"]
    await client.patch("/api/v1/companies/me", json={
        "vacation_from_schedule": True, "vacation_default_days": 30,
    }, headers=abs_headers)
    inv = await client.post("/api/v1/users/invite", json={
        "first_name": "Teil", "last_name": "Zeit", "email": "teilzeit@absenceag.de",
        "role": "EMPLOYEE", "initial_password": "Secret123",
    }, headers=abs_headers)
    uid = inv.json()["id"]
    await client.patch(f"/api/v1/users/{uid}", json={"work_schedule_id": sid}, headers=abs_headers)
    yr = date.today().year
    bal = await client.get(f"/api/v1/absences/balance/{uid}?year={yr}", headers=abs_headers)
    assert bal.status_code == 200, bal.text
    assert bal.json()["entitled_days"] == 18   # 30 * 3/5
    await client.patch("/api/v1/companies/me", json={"vacation_from_schedule": False}, headers=abs_headers)


# ── agent-12: Zwei-Stufen-Genehmigung ─────────────────────────────────────────

@pytest.mark.asyncio
async def test_two_stage_approval_flow(
    client: AsyncClient, abs_headers, abs_approver_headers, vacation_type_id
):
    await client.patch("/api/v1/companies/me",
                       json={"two_stage_approval_enabled": True}, headers=abs_headers)
    inv = await client.post("/api/v1/users/invite", json={
        "first_name": "Two", "last_name": "Stage", "email": "twostage@absenceag.de",
        "role": "EMPLOYEE", "initial_password": "Secret123",
    }, headers=abs_headers)
    assert inv.status_code == 201, inv.text
    login = await client.post("/api/v1/auth/login", json={
        "email": "twostage@absenceag.de", "password": "Secret123"})
    emp_h = {"Authorization": f"Bearer {login.json()['access_token']}"}

    start = _future_monday(17)
    cr = await client.post("/api/v1/absences/", json={
        "type_id": str(vacation_type_id), "start_date": str(start),
        "end_date": str(start + timedelta(days=2)),
    }, headers=emp_h)
    aid = cr.json()["id"]

    # Erste Stufe: Approver A → first_approved (noch kein Abzug)
    r1 = await client.post(f"/api/v1/absences/{aid}/approve", headers=abs_approver_headers)
    assert r1.status_code == 200, r1.text
    assert r1.json()["status"] == "first_approved"

    # Gleicher Genehmiger darf die finale Stufe nicht erteilen
    same = await client.post(f"/api/v1/absences/{aid}/approve", headers=abs_approver_headers)
    assert same.status_code == 409

    # Finale Stufe: anderer Admin → approved
    r2 = await client.post(f"/api/v1/absences/{aid}/approve", headers=abs_headers)
    assert r2.status_code == 200, r2.text
    assert r2.json()["status"] == "approved"

    await client.patch("/api/v1/companies/me",
                       json={"two_stage_approval_enabled": False}, headers=abs_headers)


@pytest.mark.asyncio
async def test_two_stage_reject_at_first_stage(
    client: AsyncClient, abs_headers, abs_approver_headers, vacation_type_id
):
    await client.patch("/api/v1/companies/me",
                       json={"two_stage_approval_enabled": True}, headers=abs_headers)
    login = await client.post("/api/v1/auth/login", json={
        "email": "twostage@absenceag.de", "password": "Secret123"})
    emp_h = {"Authorization": f"Bearer {login.json()['access_token']}"}
    start = _future_monday(19)
    cr = await client.post("/api/v1/absences/", json={
        "type_id": str(vacation_type_id), "start_date": str(start),
        "end_date": str(start + timedelta(days=1)),
    }, headers=emp_h)
    aid = cr.json()["id"]
    await client.post(f"/api/v1/absences/{aid}/approve", headers=abs_approver_headers)
    rej = await client.post(f"/api/v1/absences/{aid}/reject",
                            json={"rejection_reason": "Doch nicht"}, headers=abs_headers)
    assert rej.status_code == 200, rej.text
    assert rej.json()["status"] == "rejected"
    await client.patch("/api/v1/companies/me",
                       json={"two_stage_approval_enabled": False}, headers=abs_headers)
