from models import Annotation, Frame, Mask, ProcessingTask, Project, User
from routers.auth import create_access_token, hash_password


def test_project_crud_and_frames(client, monkeypatch):
    monkeypatch.setattr("routers.projects.get_presigned_url", lambda key, expires=3600: f"http://storage/{key}")

    created = client.post("/api/projects", json={
        "name": "Demo",
        "description": "desc",
        "thumbnail_url": "thumb.jpg",
        "parse_fps": 12,
    })
    assert created.status_code == 201
    project_id = created.json()["id"]

    frame = client.post(f"/api/projects/{project_id}/frames", json={
        "project_id": project_id,
        "frame_index": 0,
        "image_url": "frames/0.jpg",
        "width": 640,
        "height": 360,
    })
    assert frame.status_code == 201
    frame_id = frame.json()["id"]

    listing = client.get("/api/projects")
    assert listing.status_code == 200
    assert listing.json()[0]["frame_count"] == 1
    assert listing.json()[0]["thumbnail_url"] == "http://storage/thumb.jpg"

    frames = client.get(f"/api/projects/{project_id}/frames")
    assert frames.status_code == 200
    assert frames.json()[0]["image_url"] == "http://storage/frames/0.jpg"

    single_frame = client.get(f"/api/projects/{project_id}/frames/{frame_id}")
    assert single_frame.status_code == 200
    assert single_frame.json()["frame_index"] == 0

    updated = client.patch(f"/api/projects/{project_id}", json={"name": "Renamed", "status": "ready"})
    assert updated.status_code == 200
    assert updated.json()["name"] == "Renamed"
    assert updated.json()["status"] == "ready"

    empty_name = client.patch(f"/api/projects/{project_id}", json={"name": "   "})
    assert empty_name.status_code == 400

    deleted = client.delete(f"/api/projects/{project_id}")
    assert deleted.status_code == 204
    assert client.get(f"/api/projects/{project_id}").status_code == 404


def test_delete_project_cascades_related_records(client, db_session):
    created = client.post("/api/projects", json={"name": "Cascade Delete"})
    assert created.status_code == 201
    project_id = created.json()["id"]

    frame = client.post(f"/api/projects/{project_id}/frames", json={
        "project_id": project_id,
        "frame_index": 0,
        "image_url": "frames/0.jpg",
        "width": 640,
        "height": 360,
    })
    assert frame.status_code == 201
    frame_id = frame.json()["id"]

    annotation = client.post("/api/ai/annotate", json={
        "project_id": project_id,
        "frame_id": frame_id,
        "mask_data": {"polygons": [[[0.1, 0.1], [0.2, 0.1], [0.2, 0.2]]]},
    })
    assert annotation.status_code == 201
    annotation_id = annotation.json()["id"]

    db_session.add(Mask(annotation_id=annotation_id, mask_url="masks/1.png"))
    db_session.add(ProcessingTask(task_type="parse_video", project_id=project_id, status="queued"))
    db_session.commit()

    deleted = client.delete(f"/api/projects/{project_id}")
    assert deleted.status_code == 204
    assert db_session.query(Project).filter(Project.id == project_id).count() == 0
    assert db_session.query(Frame).filter(Frame.project_id == project_id).count() == 0
    assert db_session.query(Annotation).filter(Annotation.project_id == project_id).count() == 0
    assert db_session.query(Mask).count() == 0
    assert db_session.query(ProcessingTask).filter(ProcessingTask.project_id == project_id).count() == 0


def test_copy_project_reset_copies_frame_sequence_without_annotations(client, db_session):
    created = client.post("/api/projects", json={
        "name": "Reset Source",
        "description": "desc",
        "video_path": "uploads/source.mp4",
        "thumbnail_url": "thumb.jpg",
        "status": "ready",
        "parse_fps": 12,
    })
    assert created.status_code == 201
    project_id = created.json()["id"]
    frame = client.post(f"/api/projects/{project_id}/frames", json={
        "project_id": project_id,
        "frame_index": 0,
        "image_url": "frames/source/frame_000000.jpg",
        "width": 640,
        "height": 360,
        "timestamp_ms": 0,
        "source_frame_number": 0,
    })
    assert frame.status_code == 201
    annotation = client.post("/api/ai/annotate", json={
        "project_id": project_id,
        "frame_id": frame.json()["id"],
        "mask_data": {"label": "Tumor", "polygons": [[[0.1, 0.1], [0.2, 0.1], [0.2, 0.2]]]},
    })
    assert annotation.status_code == 201

    copied = client.post(f"/api/projects/{project_id}/copy", json={"mode": "reset"})
    assert copied.status_code == 201
    copied_body = copied.json()
    assert copied_body["name"] == "Reset Source 副本"
    assert copied_body["frame_count"] == 1
    assert copied_body["video_path"] == "uploads/source.mp4"
    assert copied_body["parse_fps"] == 12

    copied_frames = db_session.query(Frame).filter(Frame.project_id == copied_body["id"]).all()
    assert len(copied_frames) == 1
    assert copied_frames[0].image_url == "frames/source/frame_000000.jpg"
    assert db_session.query(Annotation).filter(Annotation.project_id == copied_body["id"]).count() == 0


def test_copy_project_full_copies_annotations_and_mask_metadata(client, db_session):
    created = client.post("/api/projects", json={
        "name": "Full Source",
        "status": "ready",
    })
    assert created.status_code == 201
    project_id = created.json()["id"]
    frame = client.post(f"/api/projects/{project_id}/frames", json={
        "project_id": project_id,
        "frame_index": 0,
        "image_url": "frames/source/frame_000000.jpg",
        "width": 640,
        "height": 360,
    })
    assert frame.status_code == 201
    frame_id = frame.json()["id"]
    annotation = client.post("/api/ai/annotate", json={
        "project_id": project_id,
        "frame_id": frame_id,
        "mask_data": {"label": "Tumor", "polygons": [[[0.1, 0.1], [0.2, 0.1], [0.2, 0.2]]]},
        "points": [[0.1, 0.1]],
        "bbox": [0.1, 0.1, 0.1, 0.1],
    })
    assert annotation.status_code == 201
    annotation_id = annotation.json()["id"]
    db_session.add(Mask(annotation_id=annotation_id, mask_url="masks/source.png", format="png"))
    db_session.commit()

    copied = client.post(f"/api/projects/{project_id}/copy", json={"mode": "full"})
    assert copied.status_code == 201
    copied_body = copied.json()
    copied_frames = db_session.query(Frame).filter(Frame.project_id == copied_body["id"]).all()
    copied_annotations = db_session.query(Annotation).filter(Annotation.project_id == copied_body["id"]).all()

    assert copied_body["name"] == "Full Source 副本"
    assert len(copied_frames) == 1
    assert len(copied_annotations) == 1
    assert copied_annotations[0].id != annotation_id
    assert copied_annotations[0].frame_id == copied_frames[0].id
    assert copied_annotations[0].mask_data["label"] == "Tumor"
    assert copied_annotations[0].bbox == [0.1, 0.1, 0.1, 0.1]
    assert copied_annotations[0].masks[0].mask_url == "masks/source.png"


def test_project_and_frame_404s(client):
    assert client.get("/api/projects/999").status_code == 404
    assert client.patch("/api/projects/999", json={"name": "x"}).status_code == 404
    assert client.delete("/api/projects/999").status_code == 404
    assert client.post("/api/projects/999/copy", json={"mode": "reset"}).status_code == 404
    assert client.post("/api/projects/999/frames", json={
        "project_id": 999,
        "frame_index": 0,
        "image_url": "missing.jpg",
    }).status_code == 404
    assert client.get("/api/projects/999/frames").status_code == 404
    assert client.get("/api/projects/999/frames/1").status_code == 404


def test_projects_are_shared_between_authenticated_users(client, db_session):
    owner_project = client.post("/api/projects", json={"name": "Owner Project"}).json()
    other_user = User(
        username="other",
        password_hash=hash_password("pass"),
        role="annotator",
        is_active=1,
    )
    db_session.add(other_user)
    db_session.commit()
    db_session.refresh(other_user)
    other_project = Project(name="Other Project", owner_user_id=other_user.id)
    db_session.add(other_project)
    db_session.commit()
    db_session.refresh(other_project)

    listing = client.get("/api/projects")
    assert {project["id"] for project in listing.json()} == {owner_project["id"], other_project.id}
    assert client.get(f"/api/projects/{other_project.id}").status_code == 200

    original_auth = client.headers["Authorization"]
    client.headers.update({"Authorization": f"Bearer {create_access_token(other_user)}"})
    try:
        other_listing = client.get("/api/projects")
        assert {project["id"] for project in other_listing.json()} == {owner_project["id"], other_project.id}
        assert client.get(f"/api/projects/{owner_project['id']}").status_code == 200
        renamed = client.patch(f"/api/projects/{owner_project['id']}", json={"name": "Edited By Other"})
        assert renamed.status_code == 200
        assert renamed.json()["name"] == "Edited By Other"
    finally:
        client.headers.update({"Authorization": original_auth})

    assert client.get(f"/api/projects/{owner_project['id']}").json()["name"] == "Edited By Other"
