"""Staging (upload -> pending) — T-IMP-004/005/006/007/008/012/031 and
the T-IMP-011 surfacing round-trip (06-test-plan §2.2)."""

from datetime import UTC, datetime, timedelta

import pytest
from sqlalchemy import select

from app.models import PendingImport
from app.services.imports import staging
from app.services.imports.errors import ImportsError
from tests.authz.conftest import actor_for
from tests.imports.conftest import csv_of, csv_row


def upload(graph, username="adi", *, filename="clockify.csv", data=None, name=None, notes=None):
    actor = actor_for(graph, username)
    payload = data if data is not None else csv_of(csv_row()).encode()
    return staging.stage_upload(
        graph.session,
        actor,
        filename=filename,
        data=payload,
        batch_name=name,
        notes=notes,
    )


def test_imp_004_empty_file_refused(graph):
    with pytest.raises(ImportsError) as err:
        upload(graph, data=b"")
    assert err.value.summary == "Please choose a CSV file to upload."
    assert graph.session.scalars(select(PendingImport)).all() == []


def test_imp_005_oversize_file_refused(graph):
    too_big = b"x" * (staging.MAX_UPLOAD_BYTES + 1)
    with pytest.raises(ImportsError) as err:
        upload(graph, data=too_big)
    assert err.value.summary == "File too large (25MB max)."


def test_imp_006_worker_and_no_role_cannot_upload(graph):
    """T-IMP-006 (upload leg): gary (plain worker) and mallory (no
    role) both lack can_manage_imports."""
    for username in ("gary", "mallory"):
        with pytest.raises(ImportsError) as err:
            upload(graph, username)
        assert err.value.code == "NOT_ALLOWED"
        assert err.value.summary == "You do not have permission to import."


def test_imp_007_no_account_fails_fast_before_staging(graph):
    """An identity with no users row resolves to a zero-capability
    actor — refused BEFORE any staging row exists."""
    actor = actor_for(graph, "ghost")  # no users row at all
    with pytest.raises(ImportsError):
        staging.stage_upload(
            graph.session, actor, filename="x.csv", data=csv_of(csv_row()).encode()
        )
    assert graph.session.scalars(select(PendingImport)).all() == []


def test_imp_008_batch_auto_naming(graph):
    # typed name wins
    pending = upload(graph, name="  My batch  ")
    assert pending.batch_name == "My batch"
    # blank name -> date-range label
    pending = upload(graph)
    assert pending.batch_name == "Jun 2, 2026"
    # no usable range is impossible post-parse (start dates are
    # mandatory), so the filename fallback is unit-tested directly:
    from app.services.imports.clockify import ParseResult

    assert staging.batch_name_for("", ParseResult(), "weekly export.v2.csv") == (
        "weekly export.v2"
    )
    assert staging.batch_name_for(None, ParseResult(), None) is None


def test_imp_012_staged_row_holds_payload_and_effective_uploader(graph):
    csv = csv_of(csv_row(), csv_row(Description="Other"))
    pending = upload(graph, data=csv.encode(), notes="  weekly  ")
    assert pending.uploaded_by == graph.users["adi"].id
    assert pending.source == "clockify"
    assert pending.filename == "clockify.csv"
    assert pending.notes == "weekly"
    assert pending.total_rows == 2
    assert len(pending.parsed_entries) == 2
    entry = pending.parsed_entries[0]
    assert entry["operator"] == "Bowden Works"
    assert entry["client"] == "Brentwood"
    assert entry["project"] == "Website"
    assert entry["start_at"] == "2026-06-02 09:00:00"
    assert entry["duration_seconds"] == 5400


def test_imp_027_view_as_attributes_to_effective_user(graph):
    """ADR #033: super admin viewing-as adi uploads AS adi."""
    actor = actor_for(graph, "rian", cookies={"with_view_as": "adi"})
    assert actor.is_viewing_as and actor.username == "adi"
    pending = staging.stage_upload(
        graph.session, actor, filename="x.csv", data=csv_of(csv_row()).encode()
    )
    assert pending.uploaded_by == graph.users["adi"].id


def test_imp_011_parse_report_staged_for_the_resolver(graph):
    csv = csv_of(
        csv_row(),
        csv_row(**{"Start Date": "garbage"}),
        csv_row(**{"End Time": "99:99"}),
    )
    pending = upload(graph, data=csv.encode())
    assert len(pending.parsed_entries) == 2
    assert pending.total_rows == 3
    assert "invalid start date/time" in pending.parse_errors[0]["message"]
    assert "invalid end date/time" in pending.parse_warnings[0]["message"]


def test_parse_failure_message_bubbles(graph):
    with pytest.raises(ImportsError) as err:
        upload(graph, data=b"Project,Client\nA,B")
    assert "missing required columns" in err.value.summary


def test_imp_031_expiry_enforced(graph):
    pending = upload(graph)
    pending.expires_at = datetime.now(UTC) - timedelta(hours=1)
    graph.session.commit()

    actor = actor_for(graph, "adi")
    with pytest.raises(ImportsError) as err:
        staging.get_pending(graph.session, actor, pending.id)
    assert err.value.code == "PENDING_NOT_FOUND"
    # the expired row was purged, not just hidden
    assert graph.session.get(PendingImport, pending.id) is None

    # ...and upload-time purging clears other stale rows too
    stale = upload(graph)
    stale.expires_at = datetime.now(UTC) - timedelta(minutes=5)
    graph.session.commit()
    stale_id = stale.id
    upload(graph)
    assert graph.session.get(PendingImport, stale_id) is None


def test_imp_026_visibility_manager_own_owner_all(graph):
    pending = upload(graph, "adi")

    adi = actor_for(graph, "adi")
    assert staging.get_pending(graph.session, adi, pending.id).id == pending.id

    rian = actor_for(graph, "rian")  # owner + super admin: sees anyone's
    assert staging.get_pending(graph.session, rian, pending.id).id == pending.id

    rian_pending = staging.stage_upload(
        graph.session, rian, filename="r.csv", data=csv_of(csv_row()).encode()
    )
    with pytest.raises(ImportsError) as err:
        staging.get_pending(graph.session, adi, rian_pending.id)  # manager ≠ uploader
    assert err.value.code == "PENDING_NOT_FOUND"


def test_imp_025_cancel_deletes_pending(graph):
    pending = upload(graph)
    actor = actor_for(graph, "adi")
    staging.cancel_pending(graph.session, actor, pending.id)
    assert graph.session.get(PendingImport, pending.id) is None
