"""
Tests for file detail view improvements including reprocessing and preview endpoints.
"""

import shutil
from unittest.mock import MagicMock, patch

import pytest
from fastapi.testclient import TestClient

from app.models import FileRecord, ProcessingLog


@pytest.mark.integration
class TestFileReprocessing:
    """Tests for single file reprocessing endpoint."""

    @patch("app.api.files.process_document")
    def test_reprocess_existing_file(self, mock_process_document, client: TestClient, db_session, sample_pdf_path):
        """Test reprocessing an existing file."""
        # Setup mock
        mock_task = MagicMock()
        mock_task.id = "test-task-123"
        mock_process_document.delay.return_value = mock_task

        # Create a file record
        file_record = FileRecord(
            filehash="abc123",
            original_filename="test.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        # Add a failed processing log
        log = ProcessingLog(
            file_id=file_record.id,
            task_id="test-task-123",
            step_name="extract_metadata_with_gpt",
            status="failure",
            message="API error",
        )
        db_session.add(log)
        db_session.commit()

        # Test reprocessing
        response = client.post(f"/api/files/{file_record.id}/reprocess")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "success"
        assert "task_id" in data
        assert data["file_id"] == file_record.id
        assert data["filename"] == "test.pdf"

    def test_reprocess_nonexistent_file(self, client: TestClient):
        """Test reprocessing a file that doesn't exist."""
        response = client.post("/api/files/99999/reprocess")
        assert response.status_code == 404
        assert "not found" in response.json()["detail"].lower()

    def test_reprocess_file_missing_on_disk(self, client: TestClient, db_session):
        """Test reprocessing when local file is missing."""
        # Create a file record with non-existent local path
        file_record = FileRecord(
            filehash="xyz789",
            original_filename="missing.pdf",
            local_filename="/nonexistent/path/missing.pdf",
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        # Test reprocessing
        response = client.post(f"/api/files/{file_record.id}/reprocess")
        assert response.status_code == 400
        assert "not found on disk" in response.json()["detail"].lower()


@pytest.mark.integration
class TestSubtaskRetry:
    """Tests for per-subtask retry endpoint."""

    def test_retry_subtask_invalid_file(self, client: TestClient):
        """Test retrying a subtask for nonexistent file."""
        response = client.post("/api/files/99999/retry-subtask?subtask_name=upload_to_dropbox")
        assert response.status_code == 404
        assert "not found" in response.json()["detail"].lower()

    def test_retry_subtask_invalid_task_name(self, client: TestClient, db_session, sample_pdf_path):
        """Test retrying with invalid subtask name."""
        # Create a file record
        file_record = FileRecord(
            filehash="retry123",
            original_filename="retry.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        # Test with invalid subtask name
        response = client.post(f"/api/files/{file_record.id}/retry-subtask?subtask_name=invalid_task")
        assert response.status_code == 400
        assert "invalid subtask name" in response.json()["detail"].lower()

    def test_retry_subtask_missing_processed_file(self, client: TestClient, db_session, sample_pdf_path):
        """Test retrying when processed file is missing."""
        # Create a file record
        file_record = FileRecord(
            filehash="retry456",
            original_filename="retry2.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        # Test retry (processed file won't exist)
        response = client.post(f"/api/files/{file_record.id}/retry-subtask?subtask_name=upload_to_dropbox")
        assert response.status_code == 400
        assert "processed file not found" in response.json()["detail"].lower()

    @patch("app.api.files.process_document")
    def test_retry_pipeline_step_process_document(
        self, mock_process_document, client: TestClient, db_session, sample_pdf_path
    ):
        """Test retrying the process_document pipeline step."""
        mock_task = MagicMock()
        mock_task.id = "retry-task-123"
        mock_process_document.delay.return_value = mock_task

        file_record = FileRecord(
            filehash="pipeline_retry1",
            original_filename="pipeline.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        response = client.post(f"/api/files/{file_record.id}/retry-subtask?subtask_name=process_document")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "success"
        assert data["subtask_name"] == "process_document"
        assert "task_id" in data

        # Verify process_document.delay was called with file_id to skip duplicate check
        mock_process_document.delay.assert_called_once()
        call_args = mock_process_document.delay.call_args
        assert call_args.kwargs.get("file_id") == file_record.id

    def test_retry_pipeline_step_ocr(self, client: TestClient, db_session, sample_pdf_path):
        """Test retrying the OCR pipeline step via the legacy azure alias.

        ``process_with_azure_document_intelligence`` is a legacy alias that the
        implementation routes to the unified ``process_with_ocr`` task.  The
        patch must therefore target ``app.tasks.process_with_ocr.process_with_ocr``.
        """
        mock_task = MagicMock()
        mock_task.id = "ocr-retry-task"

        file_record = FileRecord(
            filehash="pipeline_retry2",
            original_filename="ocr_retry.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        with patch("app.tasks.process_with_ocr.process_with_ocr") as mock_ocr:
            mock_ocr.delay.return_value = mock_task
            response = client.post(
                f"/api/files/{file_record.id}/retry-subtask?subtask_name=process_with_azure_document_intelligence"
            )
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "success"
        assert data["subtask_name"] == "process_with_azure_document_intelligence"
        mock_ocr.delay.assert_called_once()

    def test_retry_pipeline_step_ocr_direct(self, client: TestClient, db_session, sample_pdf_path):
        """Test retrying the OCR pipeline step via the ``process_with_ocr`` name."""
        mock_task = MagicMock()
        mock_task.id = "ocr-direct-retry-task"

        file_record = FileRecord(
            filehash="pipeline_retry2b",
            original_filename="ocr_direct_retry.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        with patch("app.tasks.process_with_ocr.process_with_ocr") as mock_ocr:
            mock_ocr.delay.return_value = mock_task
            response = client.post(f"/api/files/{file_record.id}/retry-subtask?subtask_name=process_with_ocr")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "success"
        assert data["subtask_name"] == "process_with_ocr"
        mock_ocr.delay.assert_called_once()

    def test_retry_pipeline_step_metadata_extraction(self, client: TestClient, db_session, sample_pdf_path):
        """Test retrying the metadata extraction pipeline step."""
        mock_task = MagicMock()
        mock_task.id = "gpt-retry-task"

        file_record = FileRecord(
            filehash="pipeline_retry3",
            original_filename="gpt_retry.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        with patch("app.tasks.extract_metadata_with_gpt.extract_metadata_with_gpt") as mock_gpt:
            mock_gpt.delay.return_value = mock_task
            response = client.post(f"/api/files/{file_record.id}/retry-subtask?subtask_name=extract_metadata_with_gpt")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "success"
        assert data["subtask_name"] == "extract_metadata_with_gpt"

    def test_retry_pipeline_step_embed_metadata(self, client: TestClient, db_session, sample_pdf_path):
        """Test retrying embed_metadata_into_pdf re-triggers metadata extraction."""
        mock_task = MagicMock()
        mock_task.id = "embed-retry-task"

        file_record = FileRecord(
            filehash="pipeline_retry4",
            original_filename="embed_retry.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        with patch("app.tasks.extract_metadata_with_gpt.extract_metadata_with_gpt") as mock_extract:
            mock_extract.delay.return_value = mock_task
            response = client.post(f"/api/files/{file_record.id}/retry-subtask?subtask_name=embed_metadata_into_pdf")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "success"
        assert data["subtask_name"] == "embed_metadata_into_pdf"
        # Verify extract_metadata_with_gpt.delay was called (which chains into embed)
        mock_extract.delay.assert_called_once()

    def test_retry_pipeline_step_missing_local_file(self, client: TestClient, db_session):
        """Test retrying a pipeline step when local file is missing."""
        file_record = FileRecord(
            filehash="pipeline_retry5",
            original_filename="missing.pdf",
            local_filename="/nonexistent/path/missing.pdf",
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        response = client.post(
            f"/api/files/{file_record.id}/retry-subtask?subtask_name=process_with_azure_document_intelligence"
        )
        assert response.status_code == 400
        assert "not found on disk" in response.json()["detail"].lower()

    def test_retry_embed_metadata_with_processed_file(self, client: TestClient, db_session, sample_pdf_path, tmp_path):
        """Test retrying embed_metadata_into_pdf when file is in processed directory."""
        mock_task = MagicMock()
        mock_task.id = "embed-processed-retry-task"

        # Create processed directory and copy file there
        processed_dir = tmp_path / "processed"
        processed_dir.mkdir(exist_ok=True)
        processed_file = processed_dir / "processed_doc.pdf"

        # Copy the sample PDF to processed directory
        shutil.copy(sample_pdf_path, processed_file)

        # Create file record with non-existent local_filename but existing processed_file_path
        file_record = FileRecord(
            filehash="pipeline_retry6",
            original_filename="processed_doc.pdf",
            local_filename="/nonexistent/tmp/doc.pdf",  # File no longer in tmp
            processed_file_path=str(processed_file),  # But exists in processed
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        with patch("app.tasks.extract_metadata_with_gpt.extract_metadata_with_gpt") as mock_extract:
            mock_extract.delay.return_value = mock_task
            response = client.post(f"/api/files/{file_record.id}/retry-subtask?subtask_name=embed_metadata_into_pdf")

        # Should succeed because file exists in processed directory
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "success"
        assert data["subtask_name"] == "embed_metadata_into_pdf"
        # Verify extract_metadata_with_gpt.delay was called with full path
        mock_extract.delay.assert_called_once()
        call_args = mock_extract.delay.call_args
        # First argument should be the full path to the processed file
        assert str(processed_file) in str(call_args[0][0])


@pytest.mark.integration
class TestFilePreview:
    """Tests for file preview endpoint."""

    def test_preview_original_file(self, client: TestClient, db_session, sample_pdf_path):
        """Test getting original file preview."""
        # Create a file record
        file_record = FileRecord(
            filehash="def456",
            original_filename="preview.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        # Test preview
        response = client.get(f"/api/files/{file_record.id}/preview?version=original")
        assert response.status_code == 200
        assert response.headers["content-type"].startswith("application/pdf")
        # Verify file is set to display inline, not download
        assert "content-disposition" in response.headers
        assert "inline" in response.headers["content-disposition"]

    def test_preview_processed_file_not_found(self, client: TestClient, db_session, sample_pdf_path):
        """Test getting processed file preview when it doesn't exist."""
        # Create a file record
        file_record = FileRecord(
            filehash="ghi789",
            original_filename="processed.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        # Test preview (processed version should not exist)
        response = client.get(f"/api/files/{file_record.id}/preview?version=processed")
        assert response.status_code == 404
        assert "not found" in response.json()["detail"].lower()

    def test_preview_nonexistent_file(self, client: TestClient):
        """Test preview for a file that doesn't exist."""
        response = client.get("/api/files/99999/preview?version=original")
        assert response.status_code == 404
        assert "not found" in response.json()["detail"].lower()

    def test_preview_invalid_version(self, client: TestClient, db_session, sample_pdf_path):
        """Test preview with invalid version parameter."""
        # Create a file record
        file_record = FileRecord(
            filehash="jkl012",
            original_filename="test.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        # Test with invalid version
        response = client.get(f"/api/files/{file_record.id}/preview?version=invalid")
        assert response.status_code == 400
        assert "invalid version" in response.json()["detail"].lower()


@pytest.mark.integration
class TestFileDetailView:
    """Tests for enhanced file detail view."""

    def test_file_detail_view_with_logs(self, client: TestClient, db_session, sample_pdf_path):
        """Test file detail view returns enhanced data."""
        # Create a file record
        file_record = FileRecord(
            filehash="mno345",
            original_filename="detail.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        # Add processing logs
        logs = [
            ProcessingLog(
                file_id=file_record.id,
                task_id="task-1",
                step_name="hash_file",
                status="success",
                message="File hashed successfully",
            ),
            ProcessingLog(
                file_id=file_record.id,
                task_id="task-1",
                step_name="create_file_record",
                status="success",
                message="File record created",
            ),
            ProcessingLog(
                file_id=file_record.id,
                task_id="task-1",
                step_name="extract_metadata_with_gpt",
                status="failure",
                message="API rate limit exceeded",
            ),
        ]
        for log in logs:
            db_session.add(log)
        db_session.commit()

        # Test detail view
        response = client.get(f"/files/{file_record.id}/detail")
        assert response.status_code == 200
        # Check that response contains HTML with file information
        assert b"File Information" in response.content
        assert b"detail.pdf" in response.content
        assert b"Processing History" in response.content

    def test_file_detail_view_with_upload_branches(self, client: TestClient, db_session, sample_pdf_path):
        """Test file detail view with upload subtask branches."""
        # Create a file record
        file_record = FileRecord(
            filehash="branch123",
            original_filename="branches.pdf",
            local_filename=sample_pdf_path,
            file_size=1024,
            mime_type="application/pdf",
        )
        db_session.add(file_record)
        db_session.commit()
        db_session.refresh(file_record)

        # Add processing logs including upload branches
        logs = [
            ProcessingLog(
                file_id=file_record.id,
                task_id="task-1",
                step_name="send_to_all_destinations",
                status="success",
                message="Queued uploads",
            ),
            ProcessingLog(
                file_id=file_record.id,
                task_id="task-2",
                step_name="upload_to_dropbox",
                status="success",
                message="Uploaded to Dropbox",
            ),
            ProcessingLog(
                file_id=file_record.id,
                task_id="task-3",
                step_name="upload_to_s3",
                status="failure",
                message="S3 connection error",
            ),
            ProcessingLog(
                file_id=file_record.id,
                task_id="task-4",
                step_name="upload_to_nextcloud",
                status="success",
                message="Uploaded to Nextcloud",
            ),
        ]
        for log in logs:
            db_session.add(log)
        db_session.commit()

        # Test detail view
        response = client.get(f"/files/{file_record.id}/detail")
        assert response.status_code == 200
        # Check that response contains branching visualization elements
        assert b"Process Flow Visualization" in response.content
        assert b"Processing Status Summary" in response.content
        # Should have upload branches
        assert b"Dropbox" in response.content or b"dropbox" in response.content

    def test_file_detail_view_nonexistent(self, client: TestClient):
        """Test file detail view for nonexistent file."""
        response = client.get("/files/99999/detail")
        assert response.status_code == 200  # Returns page with error message
        assert b"not found" in response.content.lower()


@pytest.mark.unit
class TestProcessingFlowComputation:
    """Tests for the _compute_processing_flow function."""

    def test_flow_with_upload_branches(self, db_session):
        """Test that upload tasks are properly grouped as branches."""
        from app.views.files import _compute_processing_flow

        # Create mock logs
        class MockLog:
            def __init__(self, step_name, status, message, timestamp, task_id):
                self.step_name = step_name
                self.status = status
                self.message = message
                self.timestamp = timestamp
                self.task_id = task_id

        logs = [
            MockLog("hash_file", "success", "Hashed", None, "task-1"),
            MockLog("send_to_all_destinations", "success", "Queued", None, "task-2"),
            MockLog("upload_to_dropbox", "success", "Uploaded", None, "task-3"),
            MockLog("upload_to_s3", "failure", "Failed", None, "task-4"),
        ]

        flow = _compute_processing_flow(logs)

        # Find the upload stage
        upload_stage = None
        for stage in flow:
            if stage.get("is_branch_parent"):
                upload_stage = stage
                break

        assert upload_stage is not None
        assert "branches" in upload_stage
        assert len(upload_stage["branches"]) == 2

        # Check branch details
        branches = {b["key"]: b for b in upload_stage["branches"]}
        assert "upload_to_dropbox" in branches
        assert branches["upload_to_dropbox"]["status"] == "success"
        assert "upload_to_s3" in branches
        assert branches["upload_to_s3"]["status"] == "failure"
        assert branches["upload_to_s3"]["can_retry"] is True


@pytest.mark.unit
class TestStepSummary:
    """Tests for the _compute_step_summary function."""

    def test_summary_with_mixed_statuses(self):
        """Test step summary with various statuses."""
        from datetime import datetime, timedelta

        from app.views.files import _compute_step_summary

        # Create mock logs
        class MockLog:
            def __init__(self, step_name, status, timestamp):
                self.step_name = step_name
                self.status = status
                self.timestamp = timestamp

        now = datetime.now()
        logs = [
            MockLog("check_text", "success", now - timedelta(minutes=5)),
            MockLog("create_file_record", "success", now - timedelta(minutes=4)),
            MockLog("extract_metadata_with_gpt", "failure", now - timedelta(minutes=3)),
            MockLog("upload_to_dropbox", "success", now - timedelta(minutes=2)),
            MockLog("upload_to_s3", "failure", now - timedelta(minutes=1)),
            MockLog("upload_to_nextcloud", "in_progress", now),
        ]

        summary = _compute_step_summary(logs)

        assert "main" in summary
        assert "uploads" in summary
        assert summary["total_main_steps"] == 3
        assert summary["total_upload_tasks"] == 3
        assert summary["uploads"]["success"] == 1
        assert summary["uploads"]["failure"] == 1
        assert summary["uploads"]["in_progress"] == 1
