from unittest.mock import patch

import pytest

from redenlab_extract import Asset, RedenLabSession
from redenlab_extract.exceptions import (
    AuthenticationError,
    ConfigurationError,
    UploadError,
    ValidationError,
)


class TestRedenLabSession:
    """Tests for RedenLabSession initialization and configuration."""

    @patch("redenlab_extract.session.get_merged_config")
    def test_session_init_with_explicit_credentials(self, mock_config):
        """Session initializes with api_key and base_url parameters."""
        mock_config.return_value = {
            "api_key": "sk_test_12345",
            "base_url": "https://api.test.example.com",
        }

        session = RedenLabSession(api_key="sk_test_12345", base_url="https://api.test.example.com")

        assert session.api_key == "sk_test_12345"
        assert session.base_url == "https://api.test.example.com"
        mock_config.assert_called_once()

    @patch("redenlab_extract.session.get_merged_config")
    @patch("redenlab_extract.session.get_default_base_url")
    def test_session_init_with_missing_base_url_call_get_default_base_url(
        self, default_base_url, mock_config
    ):
        """Session calls get_default_base_url when base_url is None."""
        mock_config.return_value = {
            "api_key": "sk_test_12345",
            "base_url": None,
        }

        default_base_url.return_value = "https://this_is_base_url.com"

        session = RedenLabSession(
            api_key="sk_test_12345",
        )

        assert session.api_key == "sk_test_12345"
        assert session.base_url == "https://this_is_base_url.com"
        mock_config.assert_called_once()

    @patch("redenlab_extract.session.get_merged_config")
    def test_session_init_missing_api_key_raises(self, mock_config):
        """AuthenticationError raised when no API key found."""
        mock_config.return_value = {
            "api_key": None,  # Missing!
            "base_url": "https://api.test.example.com",
        }

        with pytest.raises(AuthenticationError, match="No API key found"):
            RedenLabSession()

    @patch("redenlab_extract.session.get_default_base_url")
    @patch("redenlab_extract.session.get_merged_config")
    def test_session_init_missing_base_url_raises(self, mock_config, mock_default_url):
        """ConfigurationError raised when no base_url found."""
        mock_config.return_value = {
            "api_key": "sk_test_12345",
            "base_url": None,  # Missing!
        }
        mock_default_url.return_value = None  # No fallback either

        with pytest.raises(ConfigurationError, match="No API base URL"):
            RedenLabSession(api_key="sk_test_12345")

    @patch("redenlab_extract.session.get_merged_config")
    def test_session_init_from_env_vars_or_config_file(self, mock_config):
        """Session loads credentials from environment variables."""
        # Simulate get_merged_config loading from env vars
        mock_config.return_value = {
            "api_key": "sk_env_key_12345",
            "base_url": "https://api.env.example.com",
        }

        # No explicit parameters - should use env vars via get_merged_config
        session = RedenLabSession()

        assert session.api_key == "sk_env_key_12345"
        assert session.base_url == "https://api.env.example.com"
        mock_config.assert_called_once()

    @patch("redenlab_extract.session.upload_to_presigned_url")
    @patch("redenlab_extract.session.api.request_presigned_url")
    @patch("redenlab_extract.session.get_merged_config")
    def test_upload_returns_asset(self, mock_config, mock_presigned, mock_upload, tmp_path):
        """upload() returns Asset with correct asset_id and metadata."""
        # Setup session
        mock_config.return_value = {
            "api_key": "sk_test_12345",
            "base_url": "https://api.test.example.com",
        }
        session = RedenLabSession(api_key="sk_test_12345", base_url="https://api.test.example.com")

        # Setup presigned URL response
        mock_presigned.return_value = (
            "asset-abc-123",  # asset_id
            "https://s3.amazonaws.com/presigned-url",  # upload_url
            3600,  # expires_in
        )
        mock_upload.return_value = None

        # Create a test file
        test_file = tmp_path / "test_audio.wav"
        test_file.write_bytes(b"RIFF" + b"\x00" * 40)  # Minimal WAV header

        # Upload the file
        asset = session.upload(str(test_file))

        # Verify asset properties
        assert isinstance(asset, Asset)
        assert asset.asset_id == "asset-abc-123"
        assert asset.filename == "test_audio.wav"
        assert asset.content_type == "audio/wav"

        # Verify API calls
        mock_presigned.assert_called_once()
        mock_upload.assert_called_once()

    @patch("redenlab_extract.session.upload_to_presigned_url")
    @patch("redenlab_extract.session.api.request_presigned_url")
    @patch("redenlab_extract.session.get_merged_config")
    def test_upload_propagates_upload_error(
        self, mock_config, mock_presigned, mock_upload, tmp_path
    ):
        """Session propagates UploadError from upload module."""
        # Setup session
        mock_config.return_value = {
            "api_key": "sk_test_12345",
            "base_url": "https://api.test.example.com",
        }
        session = RedenLabSession(api_key="sk_test_12345", base_url="https://api.test.example.com")

        # Setup presigned URL response
        mock_presigned.return_value = (
            "asset-abc-123",
            "https://s3.amazonaws.com/presigned-url",
            3600,
        )

        # Mock upload to raise UploadError
        mock_upload.side_effect = UploadError("S3 upload failed (HTTP 403)")

        # Create a test file
        test_file = tmp_path / "test_audio.wav"
        test_file.write_bytes(b"RIFF" + b"\x00" * 40)

        # Upload should propagate the UploadError
        with pytest.raises(UploadError, match="S3 upload failed"):
            session.upload(str(test_file))

    @patch("redenlab_extract.session.get_merged_config")
    def test_upload_invalid_file_path_raises(self, mock_config):
        """ValidationError raised for non-existent file."""
        # Setup session
        mock_config.return_value = {
            "api_key": "sk_test_12345",
            "base_url": "https://api.test.example.com",
        }
        session = RedenLabSession(api_key="sk_test_12345", base_url="https://api.test.example.com")

        # Try to upload non-existent file
        with pytest.raises(ValidationError, match="does not exist"):
            session.upload("/nonexistent/path/to/file.wav")

    @patch("redenlab_extract.session.get_merged_config")
    def test_upload_unsupported_format_raises(self, mock_config, tmp_path):
        """ValidationError raised for unsupported audio format."""
        # Setup session
        mock_config.return_value = {
            "api_key": "sk_test_12345",
            "base_url": "https://api.test.example.com",
        }
        session = RedenLabSession(api_key="sk_test_12345", base_url="https://api.test.example.com")

        # Create an unsupported file type
        test_file = tmp_path / "document.txt"
        test_file.write_text("This is not an audio file")

        # Try to upload unsupported format
        with pytest.raises(ValidationError, match="Unsupported file format"):
            session.upload(str(test_file))

    @patch("redenlab_extract.session.upload_to_presigned_url")
    @patch("redenlab_extract.session.api.request_presigned_url")
    @patch("redenlab_extract.session.get_merged_config")
    def test_upload_chunked_short_file(self, mock_config, mock_presigned, mock_upload, tmp_path):
        """Returns single asset for file shorter than chunk_duration."""
        # Setup session
        mock_config.return_value = {
            "api_key": "sk_test_12345",
            "base_url": "https://api.test.example.com",
        }
        session = RedenLabSession(api_key="sk_test_12345", base_url="https://api.test.example.com")

        # Setup presigned URL response
        mock_presigned.return_value = (
            "asset-short-123",
            "https://s3.amazonaws.com/presigned-url",
            3600,
        )
        mock_upload.return_value = None

        # Create a test file
        test_file = tmp_path / "short_audio.wav"
        test_file.write_bytes(b"RIFF" + b"\x00" * 40)

        # Mock _get_audio_duration to return 30s (< 50s default chunk_duration)
        with patch.object(RedenLabSession, "_get_audio_duration", return_value=30.0):
            chunks = session.upload_chunked(str(test_file))

        # Should return single asset (not chunked)
        assert len(chunks) == 1
        assert chunks[0].asset_id == "asset-short-123"
        assert chunks[0].parent_asset_id is None  # No parent for single asset

    @patch("redenlab_extract.session.upload_to_presigned_url")
    @patch("redenlab_extract.session.api.request_presigned_url")
    @patch("redenlab_extract.session.get_merged_config")
    def test_upload_chunked_long_file(self, mock_config, mock_presigned, mock_upload, tmp_path):
        """Returns multiple assets with parent_asset_id for long files."""
        # Setup session
        mock_config.return_value = {
            "api_key": "sk_test_12345",
            "base_url": "https://api.test.example.com",
        }
        session = RedenLabSession(api_key="sk_test_12345", base_url="https://api.test.example.com")

        # Create a test file
        test_file = tmp_path / "long_audio.wav"
        test_file.write_bytes(b"RIFF" + b"\x00" * 40)

        # Mock presigned URL to return different IDs for parent and chunks
        presigned_call_count = 0

        def mock_presigned_side_effect(*args, **kwargs):
            nonlocal presigned_call_count
            presigned_call_count += 1
            if presigned_call_count == 1:
                # First call: parent file
                return ("parent-asset-123", "https://s3.../parent", 3600)
            else:
                # Subsequent calls: chunks
                return (
                    f"chunk-{presigned_call_count-1}",
                    f"https://s3.../chunk-{presigned_call_count-1}",
                    3600,
                )

        mock_presigned.side_effect = mock_presigned_side_effect
        mock_upload.return_value = None

        # Mock _get_audio_duration to return 120s (> 50s default)
        # Mock _split_audio_into_chunks to return 3 chunk file paths
        chunk_files = [
            tmp_path / "chunk_0.wav",
            tmp_path / "chunk_1.wav",
            tmp_path / "chunk_2.wav",
        ]
        for f in chunk_files:
            f.write_bytes(b"RIFF" + b"\x00" * 40)

        with patch.object(RedenLabSession, "_get_audio_duration", return_value=120.0):
            with patch.object(
                RedenLabSession,
                "_split_audio_into_chunks",
                return_value=[str(f) for f in chunk_files],
            ):
                chunks = session.upload_chunked(str(test_file))

        # Should return multiple chunks
        assert len(chunks) == 3

        # All chunks should have the same parent_asset_id
        for i, chunk in enumerate(chunks):
            assert chunk.parent_asset_id == "parent-asset-123"
            assert chunk.chunk_index == i
            assert chunk.asset_id == f"chunk-{i+1}"
