import requests
from typing import Dict, List, Optional, Any
from utils.logging_config import get_logger
from config.settings import config_manager
import time

logger = get_logger("listenbrainz_client")

class ListenBrainzClient:
    """Client for interacting with ListenBrainz API"""

    def __init__(self, token=None, base_url=None):
        # Use provided params or fall back to global config
        if base_url is not None:
            custom_url = base_url
        else:
            custom_url = config_manager.get("listenbrainz.base_url", "")
        if custom_url:
            # Strip trailing slashes and ensure /1 API version suffix
            custom_url = custom_url.rstrip('/')
            if not custom_url.endswith('/1'):
                custom_url += '/1'
            self.base_url = custom_url
        else:
            self.base_url = "https://api.listenbrainz.org/1"
        self.token = token if token is not None else config_manager.get("listenbrainz.token", "")
        self.username = None

        # Create a session for connection pooling
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'SoulSync/1.0'
        })

        if self.token:
            # Validate token and get username
            self._validate_and_get_username()

    def _make_request_with_retry(self, method: str, url: str, max_retries: int = 3, **kwargs):
        """Make HTTP request with retry logic"""
        for attempt in range(max_retries):
            try:
                if method.lower() == 'get':
                    response = self.session.get(url, **kwargs)
                elif method.lower() == 'post':
                    response = self.session.post(url, **kwargs)
                else:
                    response = self.session.request(method, url, **kwargs)

                return response
            except (requests.exceptions.ConnectionError,
                    requests.exceptions.Timeout,
                    ConnectionResetError) as e:
                if attempt < max_retries - 1:
                    wait_time = (attempt + 1) * 2  # Exponential backoff
                    logger.warning(f"Connection error (attempt {attempt + 1}/{max_retries}), retrying in {wait_time}s: {e}")
                    time.sleep(wait_time)
                else:
                    logger.error(f"Failed after {max_retries} attempts: {e}")
                    raise

        return None

    def _validate_and_get_username(self):
        """Validate token and retrieve username"""
        try:
            url = f"{self.base_url}/validate-token"
            headers = {'Authorization': f'Token {self.token}'}
            response = self._make_request_with_retry('get', url, headers=headers, timeout=10)

            if response and response.status_code == 200:
                data = response.json()
                if data.get('valid'):
                    self.username = data.get('user_name')
                    logger.info(f"ListenBrainz authenticated as: {self.username}")
                    return True

            logger.warning("Invalid ListenBrainz token")
            return False
        except Exception as e:
            logger.error(f"Error validating ListenBrainz token: {e}")
            return False

    def is_authenticated(self):
        """Check if client is authenticated"""
        return bool(self.token and self.username)

    def submit_listens(self, listens: List[Dict]) -> bool:
        """Submit play events to ListenBrainz.

        Args:
            listens: list of dicts with {artist, track, album, timestamp (unix int)}

        Returns:
            True if submission succeeded.
        """
        if not self.is_authenticated():
            return False

        if not listens:
            return True

        # Build payload per ListenBrainz API spec
        payload_listens = []
        for listen in listens:
            ts = listen.get('timestamp')
            if not ts:
                continue
            # Convert ISO string to unix timestamp if needed
            if isinstance(ts, str):
                try:
                    from datetime import datetime
                    dt = datetime.fromisoformat(ts.replace('Z', '+00:00'))
                    ts = int(dt.timestamp())
                except Exception:
                    continue

            track_metadata = {
                'artist_name': listen.get('artist', ''),
                'track_name': listen.get('track', ''),
            }
            if listen.get('album'):
                track_metadata['release_name'] = listen['album']

            payload_listens.append({
                'listened_at': ts,
                'track_metadata': track_metadata,
            })

        if not payload_listens:
            return True

        # Submit in batches of 1000 (ListenBrainz limit)
        for i in range(0, len(payload_listens), 1000):
            batch = payload_listens[i:i + 1000]
            try:
                url = f"{self.base_url}/submit-listens"
                response = self._make_request_with_retry(
                    'POST', url,
                    json={
                        'listen_type': 'import',
                        'payload': batch,
                    },
                    headers={
                        'Authorization': f'Token {self.token}',
                        'Content-Type': 'application/json',
                    }
                )
                if response and response.status_code == 200:
                    logger.info(f"Submitted {len(batch)} listens to ListenBrainz")
                else:
                    status = response.status_code if response else 'no response'
                    logger.warning(f"ListenBrainz submit failed: {status}")
                    return False
            except Exception as e:
                logger.error(f"ListenBrainz submit error: {e}")
                return False

        return True

    def get_playlists_created_for_user(self, count: int = 25, offset: int = 0) -> List[Dict]:
        """
        Fetch playlists created FOR the user (recommendations, personalized playlists)
        These are all public and don't require authentication
        """
        if not self.username:
            logger.warning("No username available for ListenBrainz")
            return []

        try:
            url = f"{self.base_url}/user/{self.username}/playlists/createdfor"
            params = {
                'count': count,
                'offset': offset
            }

            response = self._make_request_with_retry('get', url, params=params, timeout=15)

            if response and response.status_code == 200:
                data = response.json()
                playlists = data.get('playlists', [])
                logger.info(f"Fetched {len(playlists)} playlists created for {self.username}")
                return playlists
            elif response and response.status_code == 404:
                logger.warning(f"User {self.username} not found")
                return []
            else:
                status = response.status_code if response else 'No response'
                logger.error(f"Failed to fetch created-for playlists: {status}")
                return []

        except Exception as e:
            logger.error(f"Error fetching created-for playlists: {e}")
            return []

    def get_user_playlists(self, count: int = 25, offset: int = 0) -> List[Dict]:
        """
        Fetch user's own playlists (both public and private)
        Requires authentication
        """
        if not self.is_authenticated():
            logger.warning("Not authenticated for ListenBrainz")
            return []

        try:
            url = f"{self.base_url}/user/{self.username}/playlists"
            headers = {'Authorization': f'Token {self.token}'}
            params = {
                'count': count,
                'offset': offset
            }

            response = self._make_request_with_retry('get', url, headers=headers, params=params, timeout=15)

            if response and response.status_code == 200:
                data = response.json()
                playlists = data.get('playlists', [])
                logger.info(f"Fetched {len(playlists)} user playlists for {self.username}")
                return playlists
            elif response and response.status_code == 404:
                logger.warning(f"User {self.username} not found")
                return []
            else:
                status = response.status_code if response else 'No response'
                logger.error(f"Failed to fetch user playlists: {status}")
                return []

        except Exception as e:
            logger.error(f"Error fetching user playlists: {e}")
            return []

    def get_collaborative_playlists(self, count: int = 25, offset: int = 0) -> List[Dict]:
        """
        Fetch playlists where user is a collaborator
        Requires authentication for private playlists
        """
        if not self.is_authenticated():
            logger.warning("Not authenticated for ListenBrainz")
            return []

        try:
            url = f"{self.base_url}/user/{self.username}/playlists/collaborator"
            headers = {'Authorization': f'Token {self.token}'}
            params = {
                'count': count,
                'offset': offset
            }

            response = self._make_request_with_retry('get', url, headers=headers, params=params, timeout=15)

            if response and response.status_code == 200:
                data = response.json()
                playlists = data.get('playlists', [])
                logger.info(f"Fetched {len(playlists)} collaborative playlists for {self.username}")
                return playlists
            elif response and response.status_code == 404:
                logger.warning(f"User {self.username} not found")
                return []
            else:
                status = response.status_code if response else 'No response'
                logger.error(f"Failed to fetch collaborative playlists: {status}")
                return []

        except Exception as e:
            logger.error(f"Error fetching collaborative playlists: {e}")
            return []

    def get_playlist_details(self, playlist_mbid: str, fetch_metadata: bool = True) -> Optional[Dict]:
        """
        Fetch full playlist details including tracks

        Args:
            playlist_mbid: The MusicBrainz ID of the playlist
            fetch_metadata: Whether to fetch recording metadata (default True)
        """
        try:
            url = f"{self.base_url}/playlist/{playlist_mbid}"
            params = {}

            if not fetch_metadata:
                params['fetch_metadata'] = 'false'

            # Add auth header if we have a token (for private playlists)
            headers = {}
            if self.token:
                headers['Authorization'] = f'Token {self.token}'

            response = self._make_request_with_retry('get', url, headers=headers, params=params, timeout=20)

            if response and response.status_code == 200:
                data = response.json()
                playlist = data.get('playlist', {})
                track_count = len(playlist.get('track', []))
                logger.info(f"Fetched playlist '{playlist.get('title')}' with {track_count} tracks")
                return playlist
            elif response and response.status_code == 404:
                logger.warning(f"Playlist {playlist_mbid} not found")
                return None
            elif response and response.status_code == 401:
                logger.warning(f"Unauthorized to access playlist {playlist_mbid}")
                return None
            else:
                status = response.status_code if response else 'No response'
                logger.error(f"Failed to fetch playlist: {status}")
                return None

        except Exception as e:
            logger.error(f"Error fetching playlist details: {e}")
            return None

    def search_playlists(self, query: str) -> List[Dict]:
        """
        Search for playlists by name or description

        Args:
            query: Search query (minimum 3 characters)
        """
        if len(query) < 3:
            logger.warning("Search query must be at least 3 characters")
            return []

        try:
            url = f"{self.base_url}/playlist/search"
            params = {'query': query}

            # Add auth header if we have a token
            headers = {}
            if self.token:
                headers['Authorization'] = f'Token {self.token}'

            response = requests.get(url, headers=headers, params=params, timeout=10)

            if response.status_code == 200:
                data = response.json()
                playlists = data.get('playlists', [])
                logger.info(f"Found {len(playlists)} playlists matching '{query}'")
                return playlists
            else:
                logger.error(f"Failed to search playlists: {response.status_code}")
                return []

        except Exception as e:
            logger.error(f"Error searching playlists: {e}")
            return []
