import unittest
import sys
import os
import logging
import dotenv
import pytest
from unittest.mock import patch, MagicMock

# Add the project root to Python path for imports
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

# Create the helpers directory if it doesn't exist
helpers_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'musicround', 'helpers')
if not os.path.exists(helpers_dir):
    os.makedirs(helpers_dir)

# Create __init__.py if it doesn't exist
helpers_init = os.path.join(helpers_dir, '__init__.py')
if not os.path.exists(helpers_init):
    with open(helpers_init, 'w') as f:
        f.write('# This file makes the helpers directory a proper Python package\n')

from musicround.helpers.metadata import (
    get_song_metadata_by_isrc,
    get_musicbrainz_data,
    get_spotify_data,
    get_deezer_data,
    get_lastfm_data,
    get_openai_data,
    get_acrcloud_data
)

class TestMetadataHelper(unittest.TestCase):
    """Tests for the metadata helper functions."""
    
    def setUp(self):
        """Set up common test resources."""
        # Create a mock Flask app with logger
        self.mock_app = MagicMock()
        self.mock_app.logger = logging.getLogger("test_logger")
        self.mock_app.config = {}
        
        # Test ISRC for AC/DC's Highway to Hell
        self.test_isrc = "AUAP07900028"

    @patch('musicround.helpers.metadata.get_musicbrainz_data')
    @patch('musicround.helpers.metadata.get_spotify_data')
    @patch('musicround.helpers.metadata.get_deezer_data')
    @patch('musicround.helpers.metadata.get_lastfm_data')
    @patch('musicround.helpers.metadata.get_openai_data')
    @patch('musicround.helpers.metadata.get_acrcloud_data')  # Add the new mock for ACRCloud
    def test_acdc_highway_to_hell(self, mock_acrcloud, mock_openai, mock_lastfm, mock_deezer, 
                                  mock_spotify, mock_musicbrainz):
        """Test that AC/DC's Highway to Hell returns correct metadata."""
        # Set up mock return values
        mock_musicbrainz.return_value = {
            "title": "Highway to Hell",
            "artist_name": "AC/DC",
            "year": "1979",
            "genre": "Hard Rock"
        }
        
        mock_spotify.return_value = {
            "title": "Highway to Hell",
            "artist_name": "AC/DC",
            "year": "1979",
            "genre": "Hard Rock",
            "popularity": 82
        }
        
        mock_deezer.return_value = {
            "title": "Highway to Hell",
            "artist_name": "AC/DC",
            "year": "1979",
            "preview_url": "https://cdns-preview-8.dzcdn.net/stream/c-8f5a9d479bc54be17e2754cb9653e60b-6.mp3"
        }
        
        mock_lastfm.return_value = {
            "genre": "Classic Rock"
        }
        
        mock_openai.return_value = {
            "genre": "Rock",
            "year": "1979"
        }
        
        # Add ACRCloud mock data
        mock_acrcloud.return_value = {
            "title": "Highway to Hell",
            "artist_name": "AC/DC",
            "year": "1979",
            "genre": ["Rock", "Hard Rock"],
            "spotify_id": "2zYzyRzz6pRmhPzyfMEC8s",
            "deezer_id": "89077521",
            "spotify_preview_url": "https://audio-ssl.spotify.com/preview/track1234.mp3"
        }
        
        # Call function with test ISRC
        metadata = get_song_metadata_by_isrc(self.test_isrc, self.mock_app)
        
        # Verify mock functions were called correctly
        mock_acrcloud.assert_called_once_with(self.test_isrc, self.mock_app)
        mock_musicbrainz.assert_called_once_with(self.test_isrc, self.mock_app.logger)
        mock_spotify.assert_called_once_with(self.test_isrc, self.mock_app)
        mock_deezer.assert_called_once_with(self.test_isrc, self.mock_app)
        
        # LastFM and OpenAI should be called with title and artist name
        mock_lastfm.assert_called_once_with("AC/DC", "Highway to Hell", self.mock_app)
        mock_openai.assert_called_once_with("AC/DC", "Highway to Hell", self.mock_app)
        
        # Assert the expected metadata values
        self.assertEqual(metadata["title"], "Highway to Hell")
        self.assertEqual(metadata["artist_name"], "AC/DC")
        self.assertEqual(metadata["year"], "1979")
        
        # Genre should be the most common one from all sources
        self.assertEqual(metadata["genre"], "Hard Rock")  # Hard Rock appears most frequently in our mocks
        
        self.assertEqual(metadata["popularity"], 82)
        # Preview URL should be from Spotify since we prioritize it
        self.assertEqual(metadata["preview_url"], 
                         "https://audio-ssl.spotify.com/preview/track1234.mp3")
        
        # Check that ACRCloud platform IDs are present
        self.assertEqual(metadata["spotify_id"], "2zYzyRzz6pRmhPzyfMEC8s")
        self.assertEqual(metadata["deezer_id"], "89077521")
        
        # Check that all sources are included
        expected_sources = ["acrcloud", "musicbrainz", "spotify", "deezer", "lastfm", "openai"]
        self.assertCountEqual(metadata["sources"], expected_sources)
        
    @patch('musicround.helpers.metadata.requests.get')
    @patch('musicround.helpers.metadata.musicbrainzngs.search_recordings')
    def test_integration_with_real_data(self, mock_mb_search, mock_requests):
        """
        Test with more realistic data structures that might be returned by APIs.
        This is still a mock test but with more complex structures.
        """
        # Set up MockResponse class for requests
        class MockResponse:
            def __init__(self, json_data, status_code):
                self.json_data = json_data
                self.status_code = status_code
                
            def json(self):
                return self.json_data
        
        # Mock MusicBrainz data
        mock_mb_search.return_value = {
            'recording-list': [{
                'title': 'Highway to Hell',
                'artist-credit': [
                    {'artist': {'name': 'AC/DC'}}
                ],
                'tag-list': [
                    {'name': 'hard rock'},
                    {'name': 'rock'},
                    {'name': 'classic rock'}
                ],
                'release-list': [
                    {'date': '1979-07-27'}
                ]
            }]
        }
        
        # Mock Last.fm response
        mock_lastfm_response = {
            'track': {
                'name': 'Highway to Hell',
                'artist': {'name': 'AC/DC'},
                'toptags': {
                    'tag': [
                        {'name': 'rock'},
                        {'name': 'classic rock'},
                        {'name': 'hard rock'}
                    ]
                }
            }
        }
        
        # Set up the mock for requests.get to return the mock response
        def mock_request_get(url, **kwargs):
            if 'audioscrobbler.com' in url:
                return MockResponse(mock_lastfm_response, 200)
            return MockResponse({}, 404)
            
        mock_requests.side_effect = mock_request_get
        
        # Since we can't easily mock everything, we'll patch the other functions
        with patch('musicround.helpers.metadata.get_spotify_data') as mock_spotify, \
             patch('musicround.helpers.metadata.get_deezer_data') as mock_deezer, \
             patch('musicround.helpers.metadata.get_openai_data') as mock_openai:
            
            mock_spotify.return_value = {}
            mock_deezer.return_value = {}
            mock_openai.return_value = {}
            
            # Only test MusicBrainz data in this test
            metadata = get_song_metadata_by_isrc(self.test_isrc, self.mock_app)
            
            # Verify the results
            self.assertEqual(metadata["title"], "Highway to Hell")
            self.assertEqual(metadata["artist_name"], "AC/DC")
            self.assertEqual(metadata["year"], "1979")
            self.assertEqual(metadata["genre"], "hard rock")  # First tag from tag-list

    @pytest.mark.skip(reason="Integration test requiring live API credentials. Run manually when needed.")
    def test_real_api_calls(self):
        """
        Test actual API calls with AC/DC's Highway to Hell.
        This test makes real API calls so it should be skipped by default.
        Run this test manually when needed by removing the skip decorator.
        """
        # Load environment variables from .env file
        dotenv.load_dotenv(os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env'))
        
        # Initialize test app with valid API keys
        real_app = MagicMock()
        real_app.logger = logging.getLogger("real_api_test")
        real_app.logger.setLevel(logging.INFO)
        
        # Add a console handler to see the logs
        console_handler = logging.StreamHandler()
        console_handler.setLevel(logging.INFO)
        real_app.logger.addHandler(console_handler)
        
        # Set up config with actual API keys from .env file
        real_app.config = {
            'LASTFM_API_KEY': os.environ.get('LASTFM_API_KEY'),
            'OPENAI_API_KEY': os.environ.get('OPENAI_API_KEY'),
            'OPENAI_MODEL': os.environ.get('OPENAI_MODEL', 'gpt-4o-mini'),
            'OPENAI_URL': os.environ.get('OPENAI_URL'),
            'ACRCLOUD_TOKEN': os.environ.get('ACRCLOUD_TOKEN'),  # Add ACRCloud token
        }
        
        # Setup Spotify client if credentials are available
        if all([os.environ.get(key) for key in ['SPOTIFY_CLIENT_ID', 'SPOTIFY_CLIENT_SECRET']]):
            import spotipy
            from spotipy.oauth2 import SpotifyClientCredentials
            
            real_app.logger.info("Setting up Spotify client...")
            spotify_client_credentials = SpotifyClientCredentials(
                client_id=os.environ.get('SPOTIFY_CLIENT_ID'),
                client_secret=os.environ.get('SPOTIFY_CLIENT_SECRET')
            )
            real_app.config['sp'] = spotipy.Spotify(client_credentials_manager=spotify_client_credentials)
        
        # Setup Deezer client
        from musicround.deezer_client import DeezerClient
        real_app.config['deezer'] = DeezerClient()
        
        # Test MusicBrainz directly (doesn't need API keys)
        real_app.logger.info("Testing MusicBrainz API...")
        mb_data = get_musicbrainz_data(self.test_isrc, real_app.logger)
        self.assertIsNotNone(mb_data)
        if mb_data:
            real_app.logger.info(f"MusicBrainz data: {mb_data}")
            self.assertEqual(mb_data.get("title"), "Highway to Hell")
            self.assertIn("AC/DC", mb_data.get("artist_name", ""))
        
        # Test Spotify if client is available
        if real_app.config.get('sp'):
            real_app.logger.info("Testing Spotify API...")
            spotify_data = get_spotify_data(self.test_isrc, real_app)
            real_app.logger.info(f"Spotify data: {spotify_data}")
            self.assertIsNotNone(spotify_data)
            if spotify_data:
                self.assertEqual(spotify_data.get("title"), "Highway to Hell")
                self.assertIn("AC/DC", spotify_data.get("artist_name", ""))
        else:
            real_app.logger.warning("Skipping Spotify test: credentials not available")
        
        # Test Deezer API
        real_app.logger.info("Testing Deezer API...")
        deezer_data = get_deezer_data(self.test_isrc, real_app)
        real_app.logger.info(f"Deezer data: {deezer_data}")
        if deezer_data:
            self.assertIsNotNone(deezer_data)
            if "title" in deezer_data:
                self.assertEqual(deezer_data.get("title"), "Highway to Hell")
            if "artist_name" in deezer_data:
                self.assertIn("AC/DC", deezer_data.get("artist_name"))
        
        # Test Last.fm if API key is available
        if real_app.config.get('LASTFM_API_KEY'):
            real_app.logger.info("Testing Last.fm API...")
            lastfm_data = get_lastfm_data("AC/DC", "Highway to Hell", real_app)
            real_app.logger.info(f"Last.fm data: {lastfm_data}")
            self.assertIsNotNone(lastfm_data)
            if lastfm_data:
                self.assertIn("genre", lastfm_data)
        else:
            real_app.logger.warning("Skipping Last.fm test: API key not available")
        
        # Test OpenAI if API key is available
        if real_app.config.get('OPENAI_API_KEY'):
            real_app.logger.info("Testing OpenAI API...")
            openai_data = get_openai_data("AC/DC", "Highway to Hell", real_app)
            real_app.logger.info(f"OpenAI data: {openai_data}")
            self.assertIsNotNone(openai_data)
            if openai_data:
                self.assertIn("year", openai_data)
                # Convert to string for comparison if it's an integer
                expected_year = "1979"
                actual_year = str(openai_data.get("year")) if openai_data.get("year") is not None else None
                self.assertEqual(actual_year, expected_year)
        else:
            real_app.logger.warning("Skipping OpenAI test: API key not available")
        
        # Test ACRCloud if token is available
        if real_app.config.get('ACRCLOUD_TOKEN'):
            from musicround.helpers.metadata import get_acrcloud_data
            real_app.logger.info("Testing ACRCloud API...")
            acrcloud_data = get_acrcloud_data(self.test_isrc, real_app)
            real_app.logger.info(f"ACRCloud data: {acrcloud_data}")
            if acrcloud_data:
                self.assertIsNotNone(acrcloud_data)
                if "title" in acrcloud_data:
                    self.assertEqual(acrcloud_data.get("title"), "Highway to Hell")
                if "artist_name" in acrcloud_data:
                    self.assertIn("AC/DC", acrcloud_data.get("artist_name"))
                # Check if platform IDs were retrieved
                if "spotify_id" in acrcloud_data:
                    self.assertIsNotNone(acrcloud_data.get("spotify_id"))
                if "deezer_id" in acrcloud_data:
                    self.assertIsNotNone(acrcloud_data.get("deezer_id"))
        else:
            real_app.logger.warning("Skipping ACRCloud test: API token not available")
        
        # Full test with all APIs
        real_app.logger.info("Testing full metadata aggregation...")
        metadata = get_song_metadata_by_isrc(self.test_isrc, real_app)
        real_app.logger.info(f"Full metadata: {metadata}")
        
        self.assertIsNotNone(metadata)
        if metadata:
            self.assertEqual(metadata.get("title"), "Highway to Hell")
            self.assertIn("AC/DC", metadata.get("artist_name", ""))
            self.assertEqual(metadata.get("year"), "1979")
            self.assertIsNotNone(metadata.get("genre"))
            self.assertIn("musicbrainz", metadata.get("sources", []))
            
            # Check if we got data from other sources as expected
            if real_app.config.get('ACRCLOUD_TOKEN'):
                self.assertIn("acrcloud", metadata.get("sources", []))
            if real_app.config.get('sp'):
                self.assertIn("spotify", metadata.get("sources", []))
            if real_app.config.get('LASTFM_API_KEY'):
                self.assertIn("lastfm", metadata.get("sources", []))
            
            # Only check for OpenAI if we have both key and we've verified it works
            if real_app.config.get('OPENAI_API_KEY') and openai_data and ('genre' in openai_data or 'year' in openai_data):
                self.assertIn("openai", metadata.get("sources", []))


if __name__ == '__main__':
    unittest.main()
