import types
from unittest.mock import patch

import pytest
from werkzeug.exceptions import NotFound

import controllers.files.image_preview as module


def unwrap(func):
    while hasattr(func, "__wrapped__"):
        func = func.__wrapped__
    return func


@pytest.fixture(autouse=True)
def mock_db():
    """
    Replace Flask-SQLAlchemy db with a plain object
    to avoid touching Flask app context entirely.
    """
    fake_db = types.SimpleNamespace(engine=object())
    module.db = fake_db


class DummyUploadFile:
    def __init__(self, mime_type="text/plain", size=10, name="test.txt", extension="txt"):
        self.mime_type = mime_type
        self.size = size
        self.name = name
        self.extension = extension


def fake_request(args: dict):
    """Return a fake request object (NOT a Flask LocalProxy)."""
    return types.SimpleNamespace(args=types.SimpleNamespace(to_dict=lambda flat=True: args))


class TestImagePreviewApi:
    @patch.object(module, "FileService")
    def test_success(self, mock_file_service):
        module.request = fake_request(
            {
                "timestamp": "123",
                "nonce": "abc",
                "sign": "sig",
            }
        )

        generator = iter([b"img"])
        mock_file_service.return_value.get_image_preview.return_value = (
            generator,
            "image/png",
        )

        api = module.ImagePreviewApi()
        get_fn = unwrap(api.get)

        response = get_fn("file-id")

        assert response.mimetype == "image/png"

    @patch.object(module, "FileService")
    def test_unsupported_file_type(self, mock_file_service):
        module.request = fake_request(
            {
                "timestamp": "123",
                "nonce": "abc",
                "sign": "sig",
            }
        )

        mock_file_service.return_value.get_image_preview.side_effect = (
            module.services.errors.file.UnsupportedFileTypeError()
        )

        api = module.ImagePreviewApi()
        get_fn = unwrap(api.get)

        with pytest.raises(module.UnsupportedFileTypeError):
            get_fn("file-id")


class TestFilePreviewApi:
    @patch.object(module, "enforce_download_for_html")
    @patch.object(module, "FileService")
    def test_basic_stream(self, mock_file_service, mock_enforce):
        module.request = fake_request(
            {
                "timestamp": "123",
                "nonce": "abc",
                "sign": "sig",
                "as_attachment": False,
            }
        )

        generator = iter([b"data"])
        upload_file = DummyUploadFile(size=100)

        mock_file_service.return_value.get_file_generator_by_file_id.return_value = (
            generator,
            upload_file,
        )

        api = module.FilePreviewApi()
        get_fn = unwrap(api.get)

        response = get_fn("file-id")

        assert response.mimetype == "application/octet-stream"
        assert response.headers["Content-Length"] == "100"
        assert "Accept-Ranges" not in response.headers
        mock_enforce.assert_called_once()

    @patch.object(module, "enforce_download_for_html")
    @patch.object(module, "FileService")
    def test_as_attachment(self, mock_file_service, mock_enforce):
        module.request = fake_request(
            {
                "timestamp": "123",
                "nonce": "abc",
                "sign": "sig",
                "as_attachment": True,
            }
        )

        generator = iter([b"data"])
        upload_file = DummyUploadFile(
            mime_type="application/pdf",
            name="doc.pdf",
            extension="pdf",
        )

        mock_file_service.return_value.get_file_generator_by_file_id.return_value = (
            generator,
            upload_file,
        )

        api = module.FilePreviewApi()
        get_fn = unwrap(api.get)

        response = get_fn("file-id")

        assert response.headers["Content-Disposition"].startswith("attachment")
        assert response.headers["Content-Type"] == "application/octet-stream"
        mock_enforce.assert_called_once()

    @patch.object(module, "FileService")
    def test_unsupported_file_type(self, mock_file_service):
        module.request = fake_request(
            {
                "timestamp": "123",
                "nonce": "abc",
                "sign": "sig",
                "as_attachment": False,
            }
        )

        mock_file_service.return_value.get_file_generator_by_file_id.side_effect = (
            module.services.errors.file.UnsupportedFileTypeError()
        )

        api = module.FilePreviewApi()
        get_fn = unwrap(api.get)

        with pytest.raises(module.UnsupportedFileTypeError):
            get_fn("file-id")


class TestWorkspaceWebappLogoApi:
    @patch.object(module, "FileService")
    @patch.object(module.TenantService, "get_custom_config")
    def test_success(self, mock_config, mock_file_service):
        mock_config.return_value = {"replace_webapp_logo": "logo-id"}
        generator = iter([b"logo"])

        mock_file_service.return_value.get_public_image_preview.return_value = (
            generator,
            "image/png",
        )

        api = module.WorkspaceWebappLogoApi()
        get_fn = unwrap(api.get)

        response = get_fn("workspace-id")

        assert response.mimetype == "image/png"

    @patch.object(module.TenantService, "get_custom_config")
    def test_logo_not_configured(self, mock_config):
        mock_config.return_value = {}

        api = module.WorkspaceWebappLogoApi()
        get_fn = unwrap(api.get)

        with pytest.raises(NotFound):
            get_fn("workspace-id")

    @patch.object(module, "FileService")
    @patch.object(module.TenantService, "get_custom_config")
    def test_unsupported_file_type(self, mock_config, mock_file_service):
        mock_config.return_value = {"replace_webapp_logo": "logo-id"}
        mock_file_service.return_value.get_public_image_preview.side_effect = (
            module.services.errors.file.UnsupportedFileTypeError()
        )

        api = module.WorkspaceWebappLogoApi()
        get_fn = unwrap(api.get)

        with pytest.raises(module.UnsupportedFileTypeError):
            get_fn("workspace-id")
