"""High priority face identification API tests."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest
from fastapi.testclient import TestClient

if TYPE_CHECKING:
    from sqlalchemy.orm import Session


class TestIdentifyFace:
    """Test face identification endpoint."""

    def test_identify_face_with_existing_person(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
        test_person,
        test_db_session: Session,
    ):
        """Verify identification with existing person."""
        response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            headers=auth_headers,
            json={"person_id": test_person.id},
        )
        
        assert response.status_code == 200
        data = response.json()
        assert data["person_id"] == test_person.id
        assert data["created_person"] is False
        assert test_face.id in data["identified_face_ids"]
        
        # Verify face is linked to person
        test_db_session.refresh(test_face)
        assert test_face.person_id == test_person.id
        assert test_face.identified_by_user_id is not None
        
        # Verify person_encoding was created
        from backend.db.models import PersonEncoding
        encoding = test_db_session.query(PersonEncoding).filter(
            PersonEncoding.face_id == test_face.id
        ).first()
        assert encoding is not None
        assert encoding.person_id == test_person.id

    def test_identify_face_create_new_person(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
        test_db_session: Session,
    ):
        """Verify person creation during identification."""
        response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            headers=auth_headers,
            json={
                "first_name": "Jane",
                "last_name": "Smith",
                "middle_name": "Middle",
                "date_of_birth": "1995-05-15",
            },
        )
        
        assert response.status_code == 200
        data = response.json()
        assert data["created_person"] is True
        assert data["person_id"] is not None
        assert test_face.id in data["identified_face_ids"]
        
        # Verify person was created
        from backend.db.models import Person
        person = test_db_session.query(Person).filter(
            Person.id == data["person_id"]
        ).first()
        assert person is not None
        assert person.first_name == "Jane"
        assert person.last_name == "Smith"
        assert person.middle_name == "Middle"
        
        # Verify face is linked
        test_db_session.refresh(test_face)
        assert test_face.person_id == person.id

    def test_identify_face_with_additional_faces(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
        test_face_2,
        test_person,
        test_db_session: Session,
    ):
        """Verify batch identification with additional faces."""
        response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            headers=auth_headers,
            json={
                "person_id": test_person.id,
                "additional_face_ids": [test_face_2.id],
            },
        )
        
        assert response.status_code == 200
        data = response.json()
        assert len(data["identified_face_ids"]) == 2
        assert test_face.id in data["identified_face_ids"]
        assert test_face_2.id in data["identified_face_ids"]
        
        # Verify both faces are linked
        test_db_session.refresh(test_face)
        test_db_session.refresh(test_face_2)
        assert test_face.person_id == test_person.id
        assert test_face_2.person_id == test_person.id

    def test_identify_face_face_not_found(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_person,
    ):
        """Verify 404 for non-existent face."""
        response = test_client.post(
            "/api/v1/faces/99999/identify",
            headers=auth_headers,
            json={"person_id": test_person.id},
        )
        
        assert response.status_code == 404
        data = response.json()
        assert "not found" in data["detail"].lower()

    def test_identify_face_person_not_found(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
    ):
        """Verify 400 when person_id is invalid."""
        response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            headers=auth_headers,
            json={"person_id": 99999},
        )
        
        assert response.status_code == 400
        data = response.json()
        assert "person_id not found" in data["detail"]

    def test_identify_face_tracks_user_id(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
        test_person,
        admin_user,
        test_db_session: Session,
    ):
        """Verify user tracking for face identification."""
        response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            headers=auth_headers,
            json={"person_id": test_person.id},
        )
        
        assert response.status_code == 200
        
        # Verify identified_by_user_id is set
        test_db_session.refresh(test_face)
        assert test_face.identified_by_user_id == admin_user.id

    def test_identify_face_creates_person_encodings(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
        test_person,
        test_db_session: Session,
    ):
        """Verify person_encodings are created for identified faces."""
        response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            headers=auth_headers,
            json={"person_id": test_person.id},
        )
        
        assert response.status_code == 200
        
        # Verify person_encoding exists
        from backend.db.models import PersonEncoding
        encoding = test_db_session.query(PersonEncoding).filter(
            PersonEncoding.face_id == test_face.id,
            PersonEncoding.person_id == test_person.id,
        ).first()
        assert encoding is not None
        assert encoding.encoding == test_face.encoding
        assert encoding.quality_score == test_face.quality_score
        assert encoding.detector_backend == test_face.detector_backend
        assert encoding.model_name == test_face.model_name

    def test_identify_face_requires_name_for_new_person(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
    ):
        """Verify validation when creating new person without required fields."""
        # Missing first_name
        response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            headers=auth_headers,
            json={"last_name": "Smith"},
        )
        assert response.status_code == 400
        assert "first_name and last_name are required" in response.json()["detail"]
        
        # Missing last_name
        response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            headers=auth_headers,
            json={"first_name": "Jane"},
        )
        assert response.status_code == 400
        assert "first_name and last_name are required" in response.json()["detail"]

    def test_identify_face_unauthenticated(
        self,
        test_client: TestClient,
        test_face,
        test_person,
    ):
        """Verify 401 when not authenticated."""
        response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            json={"person_id": test_person.id},
        )
        
        assert response.status_code == 401


class TestGetSimilarFaces:
    """Test similar faces endpoint."""

    def test_get_similar_faces_success(
        self,
        test_client: TestClient,
        test_face,
        test_face_2,
        test_db_session: Session,
    ):
        """Verify similar faces retrieval."""
        response = test_client.get(
            f"/api/v1/faces/{test_face.id}/similar"
        )
        
        assert response.status_code == 200
        data = response.json()
        assert data["base_face_id"] == test_face.id
        assert "items" in data
        assert isinstance(data["items"], list)

    def test_get_similar_faces_include_excluded(
        self,
        test_client: TestClient,
        test_face,
        test_db_session: Session,
    ):
        """Verify include_excluded parameter."""
        # Create an excluded face
        from backend.db.models import Face, Photo
        import numpy as np
        
        photo = test_db_session.query(Photo).filter(
            Photo.id == test_face.photo_id
        ).first()
        
        excluded_face = Face(
            photo_id=photo.id,
            person_id=None,
            encoding=np.random.rand(128).astype(np.float32).tobytes(),
            location='{"x": 50, "y": 50, "w": 100, "h": 100}',
            quality_score=0.70,
            face_confidence=0.85,
            detector_backend="retinaface",
            model_name="VGG-Face",
            excluded=True,
        )
        test_db_session.add(excluded_face)
        test_db_session.commit()
        
        # Test without include_excluded (should exclude excluded faces)
        response = test_client.get(
            f"/api/v1/faces/{test_face.id}/similar?include_excluded=false"
        )
        assert response.status_code == 200
        
        # Test with include_excluded=true
        response = test_client.get(
            f"/api/v1/faces/{test_face.id}/similar?include_excluded=true"
        )
        assert response.status_code == 200

    def test_get_similar_faces_face_not_found(
        self,
        test_client: TestClient,
    ):
        """Verify 404 for non-existent face."""
        response = test_client.get("/api/v1/faces/99999/similar")
        
        assert response.status_code == 404
        data = response.json()
        assert "not found" in data["detail"].lower()

    def test_get_similar_faces_returns_similarity_scores(
        self,
        test_client: TestClient,
        test_face,
    ):
        """Verify similarity scores in response."""
        response = test_client.get(
            f"/api/v1/faces/{test_face.id}/similar"
        )
        
        assert response.status_code == 200
        data = response.json()
        
        # Check response structure
        if len(data["items"]) > 0:
            item = data["items"][0]
            assert "id" in item
            assert "photo_id" in item
            assert "similarity" in item
            assert "quality_score" in item
            assert isinstance(item["similarity"], (int, float))
            assert 0 <= item["similarity"] <= 1


class TestBatchSimilarity:
    """Test batch similarity endpoint."""

    def test_batch_similarity_success(
        self,
        test_client: TestClient,
        test_face,
        test_face_2,
    ):
        """Verify batch similarity calculation."""
        response = test_client.post(
            "/api/v1/faces/batch-similarity",
            json={"face_ids": [test_face.id, test_face_2.id]},
        )
        
        assert response.status_code == 200
        data = response.json()
        assert "pairs" in data
        assert isinstance(data["pairs"], list)

    def test_batch_similarity_with_min_confidence(
        self,
        test_client: TestClient,
        test_face,
        test_face_2,
    ):
        """Verify min_confidence filter."""
        response = test_client.post(
            "/api/v1/faces/batch-similarity",
            json={
                "face_ids": [test_face.id, test_face_2.id],
                "min_confidence": 0.5,
            },
        )
        
        assert response.status_code == 200
        data = response.json()
        assert "pairs" in data
        
        # Verify all pairs meet min_confidence threshold
        for pair in data["pairs"]:
            assert pair["confidence_pct"] >= 50  # 0.5 * 100

    def test_batch_similarity_empty_list(
        self,
        test_client: TestClient,
    ):
        """Verify handling of empty face_ids list."""
        response = test_client.post(
            "/api/v1/faces/batch-similarity",
            json={"face_ids": []},
        )
        
        assert response.status_code == 200
        data = response.json()
        assert data["pairs"] == []

    def test_batch_similarity_invalid_face_ids(
        self,
        test_client: TestClient,
        test_face,
    ):
        """Verify error handling for invalid face IDs."""
        response = test_client.post(
            "/api/v1/faces/batch-similarity",
            json={"face_ids": [test_face.id, 99999]},
        )
        
        # Should still return 200, but may have fewer pairs
        # (implementation dependent - may filter out invalid IDs)
        assert response.status_code in [200, 400, 404]


class TestGetUnidentifiedFaces:
    """Test unidentified faces listing endpoint."""

    def test_get_unidentified_faces_success(
        self,
        test_client: TestClient,
        test_face,
        test_face_2,
    ):
        """Verify unidentified faces list retrieval."""
        response = test_client.get("/api/v1/faces/unidentified")
        
        assert response.status_code == 200
        data = response.json()
        assert "items" in data
        assert "page" in data
        assert "page_size" in data
        assert "total" in data
        assert isinstance(data["items"], list)
        assert data["total"] >= 2  # At least our two test faces

    def test_get_unidentified_faces_with_pagination(
        self,
        test_client: TestClient,
        test_face,
        test_face_2,
    ):
        """Verify pagination works."""
        response = test_client.get(
            "/api/v1/faces/unidentified?page=1&page_size=1"
        )
        
        assert response.status_code == 200
        data = response.json()
        assert data["page"] == 1
        assert data["page_size"] == 1
        assert len(data["items"]) <= 1

    def test_get_unidentified_faces_with_quality_filter(
        self,
        test_client: TestClient,
        test_face,
    ):
        """Verify quality filter."""
        # Test with high quality threshold
        response = test_client.get(
            "/api/v1/faces/unidentified?min_quality=0.9"
        )
        
        assert response.status_code == 200
        data = response.json()
        
        # Verify all returned faces meet quality threshold
        for item in data["items"]:
            assert item["quality_score"] >= 0.9

    def test_get_unidentified_faces_excludes_identified(
        self,
        test_client: TestClient,
        test_face,
        test_person,
        auth_headers: dict,
        test_db_session: Session,
    ):
        """Verify identified faces are excluded from results."""
        # First, verify face is in unidentified list
        response = test_client.get("/api/v1/faces/unidentified")
        assert response.status_code == 200
        initial_count = response.json()["total"]
        
        # Identify the face
        identify_response = test_client.post(
            f"/api/v1/faces/{test_face.id}/identify",
            headers=auth_headers,
            json={"person_id": test_person.id},
        )
        assert identify_response.status_code == 200
        
        # Verify face is no longer in unidentified list
        response = test_client.get("/api/v1/faces/unidentified")
        assert response.status_code == 200
        new_count = response.json()["total"]
        assert new_count < initial_count

    def test_get_unidentified_faces_with_date_filters(
        self,
        test_client: TestClient,
        test_face,
    ):
        """Verify date filtering."""
        response = test_client.get(
            "/api/v1/faces/unidentified?date_taken_from=2024-01-01&date_taken_to=2024-12-31"
        )
        
        assert response.status_code == 200
        data = response.json()
        assert "items" in data

    def test_get_unidentified_faces_invalid_date_format(
        self,
        test_client: TestClient,
    ):
        """Verify invalid date format handling."""
        response = test_client.get(
            "/api/v1/faces/unidentified?date_taken_from=invalid-date"
        )
        
        # Should handle gracefully (may return 200 with no results or 400)
        assert response.status_code in [200, 400]


class TestAutoMatch:
    """Test auto-match functionality."""

    def test_auto_match_faces_success(
        self,
        test_client: TestClient,
        test_face,
        identified_face,
        test_person,
    ):
        """Verify auto-match process."""
        response = test_client.post(
            "/api/v1/faces/auto-match",
            json={"tolerance": 0.6},
        )
        
        assert response.status_code == 200
        data = response.json()
        assert "people" in data
        assert "total_people" in data
        assert "total_matches" in data
        assert isinstance(data["people"], list)

    def test_auto_match_faces_with_tolerance(
        self,
        test_client: TestClient,
        test_face,
        identified_face,
        test_person,
    ):
        """Verify tolerance parameter affects results."""
        # Test with high tolerance (more matches)
        response_high = test_client.post(
            "/api/v1/faces/auto-match",
            json={"tolerance": 0.8},
        )
        assert response_high.status_code == 200
        
        # Test with low tolerance (fewer matches)
        response_low = test_client.post(
            "/api/v1/faces/auto-match",
            json={"tolerance": 0.4},
        )
        assert response_low.status_code == 200
        
        # Lower tolerance should generally have fewer matches
        # (though this depends on actual face similarities)
        data_high = response_high.json()
        data_low = response_low.json()
        # Note: This is a probabilistic assertion - may not always hold

    def test_auto_match_faces_auto_accept_enabled(
        self,
        test_client: TestClient,
        test_face,
        identified_face,
        test_person,
    ):
        """Verify auto-accept functionality."""
        response = test_client.post(
            "/api/v1/faces/auto-match",
            json={
                "tolerance": 0.6,
                "auto_accept": True,
                "auto_accept_threshold": 0.7,
            },
        )
        
        assert response.status_code == 200
        data = response.json()
        assert data["auto_accepted"] is True
        assert "auto_accepted_faces" in data
        assert "skipped_persons" in data
        assert "skipped_matches" in data


class TestAcceptMatches:
    """Test accept matches endpoint (via people API)."""

    def test_accept_matches_success(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
        test_face_2,
        test_person,
    ):
        """Verify accepting auto-match matches."""
        response = test_client.post(
            f"/api/v1/people/{test_person.id}/accept-matches",
            headers=auth_headers,
            json={"face_ids": [test_face.id, test_face_2.id]},
        )
        
        assert response.status_code == 200
        data = response.json()
        assert data["person_id"] == test_person.id
        assert "identified_face_ids" in data

    def test_accept_matches_tracks_user_id(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
        test_person,
        admin_user,
        test_db_session: Session,
    ):
        """Verify user tracking for accepted matches."""
        response = test_client.post(
            f"/api/v1/people/{test_person.id}/accept-matches",
            headers=auth_headers,
            json={"face_ids": [test_face.id]},
        )
        
        assert response.status_code == 200
        
        # Verify identified_by_user_id is set
        test_db_session.refresh(test_face)
        assert test_face.identified_by_user_id == admin_user.id

    def test_accept_matches_creates_person_encodings(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
        test_person,
        test_db_session: Session,
    ):
        """Verify person_encodings are created."""
        response = test_client.post(
            f"/api/v1/people/{test_person.id}/accept-matches",
            headers=auth_headers,
            json={"face_ids": [test_face.id]},
        )
        
        assert response.status_code == 200
        
        # Verify person_encoding exists
        from backend.db.models import PersonEncoding
        encoding = test_db_session.query(PersonEncoding).filter(
            PersonEncoding.face_id == test_face.id,
            PersonEncoding.person_id == test_person.id,
        ).first()
        assert encoding is not None

    def test_accept_matches_person_not_found(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_face,
    ):
        """Verify 404 for non-existent person."""
        response = test_client.post(
            "/api/v1/people/99999/accept-matches",
            headers=auth_headers,
            json={"face_ids": [test_face.id]},
        )
        
        assert response.status_code == 404

    def test_accept_matches_face_not_found(
        self,
        test_client: TestClient,
        auth_headers: dict,
        test_person,
    ):
        """Verify handling of missing faces."""
        response = test_client.post(
            f"/api/v1/people/{test_person.id}/accept-matches",
            headers=auth_headers,
            json={"face_ids": [99999]},
        )
        
        # Implementation may handle gracefully or return error
        assert response.status_code in [200, 400, 404]

