from textual.app import ComposeResult
from textual.containers import Container, Horizontal, ScrollableContainer
from textual.widgets import Button, Static, Input
from textual import work
from textual.worker import get_current_worker
import asyncio
from ...core.database.ticked_db import CalendarDB
from textual.binding import Binding
from textual.message import Message
from datetime import datetime, timedelta
import spotipy
from spotipy.oauth2 import SpotifyOAuth
import webbrowser
from dotenv import load_dotenv
import os
import threading
from .spotify_auth import start_auth_server, SpotifyCallbackHandler

load_dotenv()

class SpotifyAuth:
    def __init__(self, db: CalendarDB):
        self.db = db
        self.client_id = os.getenv("SPOTIFY_CLIENT_ID")
        self.client_secret = os.getenv("SPOTIFY_CLIENT_SECRET")
        self.redirect_uri = os.getenv("SPOTIFY_REDIRECT_URI", "http://localhost:8888/callback")
        
        self.scope = " ".join([
            "user-library-read",
            "playlist-read-private",
            "user-read-private",
            "user-read-email",
            "user-read-playback-state",
            "user-modify-playback-state",
            "user-read-currently-playing",
            "streaming",
            "app-remote-control",
            "user-read-recently-played",
            "user-top-read",
            "playlist-read-collaborative"
        ])
        
        self.sp_oauth = SpotifyOAuth(
            client_id=self.client_id,
            client_secret=self.client_secret,
            redirect_uri=self.redirect_uri,
            scope=self.scope
        )
        
        self.spotify_client = None
        self._try_restore_session()
    
    def _try_restore_session(self) -> bool:
        stored_tokens = self.db.get_spotify_tokens()
        if not stored_tokens:
            return False
            
        expiry = datetime.fromisoformat(stored_tokens['token_expiry'])
        if expiry > datetime.now():
            self.spotify_client = spotipy.Spotify(auth=stored_tokens['access_token'])
            return True
            
        try:
            token_info = self.sp_oauth.refresh_access_token(stored_tokens['refresh_token'])
            if token_info:
                self.spotify_client = spotipy.Spotify(auth=token_info['access_token'])
                self.db.save_spotify_tokens(
                    token_info['access_token'],
                    token_info['refresh_token'],
                    datetime.now() + timedelta(seconds=token_info['expires_in'])
                )
                return True
        except:
            return False
        
        return False

    def start_auth(self) -> bool:
        auth_url = self.sp_oauth.get_authorize_url()
        
        server_thread = threading.Thread(target=start_auth_server)
        server_thread.daemon = True
        server_thread.start()
        
        webbrowser.open(auth_url)
        
        server_thread.join()
        
        if SpotifyCallbackHandler.auth_code:
            try:
                token_info = self.sp_oauth.get_access_token(SpotifyCallbackHandler.auth_code)
                if token_info:
                    self.spotify_client = spotipy.Spotify(auth=token_info['access_token'])
                    self.db.save_spotify_tokens(
                        token_info['access_token'],
                        token_info['refresh_token'],
                        datetime.now() + timedelta(seconds=token_info['expires_in'])
                    )
                    return True
            except Exception as e:
                print(f"Authentication error: {e}")
                return False
        return False

class SpotifyPlayer(Container):
    def notify_current_track(self, spotify_client):
        try:
            current = spotify_client.current_playback()
            if current and current.get('item'):
                track = current['item']
                artist_names = ", ".join(artist['name'] for artist in track['artists'])
                self.notify(f"Now playing - {track['name']} by {artist_names}")
        except Exception as e:
            print(f"Error getting track info: {e}")

    def compose(self) -> ComposeResult:
        yield Horizontal(
            Button("Play/Pause", id="play-pause"),
            Button("Previous", id="prev-track"),
            Button("Next", id="next-track"),
        )
        
    @work
    async def on_button_pressed(self, event: Button.Pressed):
        spotify_client = self.app.get_spotify_client()
        if not spotify_client:
            self.notify("No Spotify client available", severity="error")
            return

        try:
            current_playback = spotify_client.current_playback()
            
            if not current_playback:
                self.notify("No active playback found. Start playing something first.", severity="error")
                return
            
            context = current_playback.get('context')
            
            if event.button.id == "play-pause":
                event.stop()
                try:
                    if current_playback["is_playing"]:
                        spotify_client.pause_playback()
                        self.notify("Paused")
                    else:
                        spotify_client.start_playback()
                        self.notify_current_track(spotify_client)
                except Exception as e:
                    print(f"Playback error: {str(e)}")
                    self.notify("Error controlling playback", severity="error")
            
            elif event.button.id == "next-track":
                event.stop()
                try:
                    if not context:
                        self.notify("No playlist context found. Try selecting a track from a playlist.", severity="warning")
                        return
                    spotify_client.next_track()
                    await asyncio.sleep(0.5) 
                    self.notify_current_track(spotify_client)
                except Exception as e:
                    print(f"Next track error: {str(e)}")
                    self.notify("Error skipping to next track", severity="error")
            
            elif event.button.id == "prev-track":
                event.stop()
                try:
                    if not context:
                        self.notify("No playlist context found. Try selecting a track from a playlist.", severity="warning")
                        return
                    spotify_client.previous_track()
                    await asyncio.sleep(0.5)  
                    self.notify_current_track(spotify_client)
                except Exception as e:
                    print(f"Previous track error: {str(e)}")
                    self.notify("Error going to previous track", severity="error")
                
        except Exception as e:
            print(f"Player error: {str(e)}")
            self.notify("Error controlling playback", severity="error")
            
class PlaylistItem(Static):
    class Selected(Message):
        def __init__(self, playlist_id: str) -> None:
            self.playlist_id = playlist_id
            super().__init__()

    def __init__(self, playlist_name: str, playlist_id: str) -> None:
        super().__init__()
        self.playlist_name = playlist_name
        self.playlist_id = playlist_id

    def compose(self) -> ComposeResult:
        self.classes = "spotify-playlist-item"
        yield Static(f"📁 {self.playlist_name}", classes="playlist-name")

    def on_click(self) -> None:
        self.post_message(self.Selected(self.playlist_id))

class SearchBar(Container):
    def compose(self) -> ComposeResult:
        yield Horizontal(
            Static("🔍", classes="search-icon"),
            Input(placeholder="Search tracks and playlists...", classes="search-input"),
            classes="search-container"
        )


class SearchResult(Static):
    class Selected(Message):
        def __init__(self, result_id: str, result_type: str, position: int = None) -> None:
            self.result_id = result_id
            self.result_type = result_type
            self.position = position
            super().__init__()

    def __init__(
        self, 
        title: str, 
        result_id: str, 
        result_type: str, 
        artist: str = "", 
        position: int = None
    ) -> None:
        super().__init__()
        self.title = title
        self.result_id = result_id
        self.result_type = result_type
        self.artist = artist
        self.position = position

    def compose(self) -> ComposeResult:
        self.classes = "spotify-track-button"
        if self.result_type == 'track':
            yield Static(f"🎵 {self.title} - {self.artist}")
        else:
            yield Static(f"📁 {self.title}")

    def on_click(self) -> None:
        self.post_message(self.Selected(
            self.result_id, 
            self.result_type,
            self.position
        ))

class LibrarySection(Container):
    def compose(self) -> ComposeResult:
        yield ScrollableContainer(
            id="playlists-container",
            classes="playlists-scroll"
        )

    def load_playlists(self, spotify_client):
        if spotify_client:
            try:
                spotify_client.current_user()
                playlists = spotify_client.current_user_playlists()
                
                container = self.query_one("#playlists-container")
                container.remove_children()
                
                container.mount(Static("Your Library", classes="section-header-lib"))
                container.mount(Static("Playlists", classes="subsection-header"))
                container.mount(PlaylistItem("Liked Songs", "liked_songs"))
                
                for playlist in playlists['items']:
                    name = playlist['name'] if playlist['name'] else "Untitled Playlist"
                    container.mount(PlaylistItem(name, playlist['id']))
                return True
                    
            except spotipy.exceptions.SpotifyException as e:
                container = self.query_one("#playlists-container")
                container.mount(Static("⚠️ Spotify API error", classes="error-message"))
                return False
                    
            except Exception as e:
                container = self.query_one("#playlists-container")
                container.mount(Static("⚠️ Error loading playlists", classes="error-message"))
                return False
        else:
            return False
            
class RecentlyPlayedView(Container):
    def compose(self) -> ComposeResult:
        yield Static("Recently Played", id="recently-played-title", classes="content-header-cont")
        yield ScrollableContainer(
            id="recently-played-container",
            classes="tracks-scroll"
        )

    def load_recent_tracks(self, spotify_client) -> None:
        if not spotify_client:
            self.notify("No spotify client")
            return

        try:
            results = spotify_client.current_user_recently_played(limit=20)
            tracks_container = self.query_one("#recently-played-container")
            if tracks_container:
                tracks_container.remove_children()
                
                for i, item in enumerate(results['items']):
                    track = item['track']
                    artist_names = ", ".join(artist['name'] for artist in track['artists'])
                    tracks_container.mount(SearchResult(
                        track['name'],
                        track['id'],
                        'track',
                        artist_names,
                        position=i
                    ))
        except Exception as e:
            self.notify(f"Error loading recent tracks: {str(e)}")
            self.query_one("#recently-played-title").update("Error loading recent tracks")

class PlaylistView(Container):
    def __init__(self) -> None:
        super().__init__()
        self.current_playlist_id = None

    def compose(self) -> ComposeResult:
        yield Static("Select a playlist", id="playlist-title", classes="content-header-cont")
        yield ScrollableContainer(
            id="tracks-container",
            classes="tracks-scroll"
        )

    def load_playlist(self, spotify_client, playlist_id: str) -> None:
        if not spotify_client:
            return

        try:
            self.current_playlist_id = playlist_id
            tracks_container = self.query_one("#tracks-container")
            if tracks_container:
                tracks_container.remove_children()

            if playlist_id == "liked_songs":
                results = spotify_client.current_user_saved_tracks()
                self.query_one("#playlist-title").update("Liked Songs")
                for i, item in enumerate(results['items']):
                    track_info = item['track']
                    artist_names = ", ".join(artist['name'] for artist in track_info['artists'])
                    tracks_container.mount(SearchResult(
                        track_info['name'],
                        track_info['id'],
                        'track',
                        artist_names,
                        position=i
                    ))
            else:
                playlist = spotify_client.playlist(playlist_id)
                self.query_one("#playlist-title").update(playlist['name'])
                for i, item in enumerate(playlist['tracks']['items']):
                    track_info = item['track']
                    if track_info:
                        artist_names = ", ".join(artist['name'] for artist in track_info['artists'])
                        tracks_container.mount(SearchResult(
                            track_info['name'],
                            track_info['id'],
                            'track',
                            artist_names,
                            position=i
                        ))
        except Exception as e:
            print(f"Error loading playlist: {e}")
            self.query_one("#playlist-title").update("Error loading playlist")

class SearchView(Container):
    def compose(self) -> ComposeResult:
        yield Static("Search", id="search-title", classes="content-header-cont")
        yield Input(placeholder="Search tracks and playlists...", id="search-input", classes="search-input")
        yield Container(
            Static("Results", classes="results-section-header"),
            ScrollableContainer(
                id="search-results-container",
                classes="tracks-scroll"
            ),
            classes="search-results-area"
        )
    def on_mount(self) -> None:
        self._search_id = 0
        self._current_worker = None
    def on_input_changed(self, event: Input.Changed) -> None:
        query = event.value.strip()

        if not query:
            results_container = self.query_one("#search-results-container")
            if results_container:
                results_container.remove_children()
            return
        self._search_id += 1

        self.run_search(query, self._search_id)
    @work
    async def run_search(self, query: str, search_id: int) -> None:
        await asyncio.sleep(0.5)

        if search_id != self._search_id:
            return
        spotify_client = self.app.get_spotify_client()
        if not spotify_client:
            return
        worker = get_current_worker()

        try:
            results = spotify_client.search(q=query, type='track,playlist', limit=10)

            if worker.is_cancelled:
                return

            results_container = self.query_one("#search-results-container")
            if results_container:
                results_container.remove_children()
                if results.get('tracks', {}).get('items'):
                    for track in results['tracks']['items']:
                        if worker.is_cancelled:
                            return
                        results_container.mount(SearchResult(
                            track['name'],
                            track['id'],
                            'track',
                            ", ".join(artist['name'] for artist in track['artists'])
                        ))
                if results.get('playlists', {}).get('items'):
                    for playlist in results['playlists']['items']:
                        if worker.is_cancelled:
                            return
                        results_container.mount(SearchResult(
                            playlist['name'],
                            playlist['id'],
                            'playlist'
                        ))
        except spotipy.exceptions.SpotifyException as e:
            if not worker.is_cancelled and search_id == self._search_id:
                print(f"Spotify API error during search: {str(e)}")
                self.app.notify("Unable to complete search", severity="error")
        except Exception as e:
            if not worker.is_cancelled and search_id == self._search_id:
                print(f"Non-critical search error (handled): {str(e)}")

class MainContent(Container):
    def compose(self) -> ComposeResult:
        yield Container(
            PlaylistView(),
            classes="playlist-view"
        )
        yield Container(
            RecentlyPlayedView(),
            classes="recently-played-view"
        )
        yield Container(
            SearchView(),
            classes="search-view"
        )

    def on_mount(self) -> None:
        print("MainContent mounted")
        if self.app.get_spotify_client():
            print("Spotify client found, loading recent tracks")
            recently_played = self.query_one(RecentlyPlayedView)
            if recently_played:
                print("Found RecentlyPlayedView, loading tracks...")
                recently_played.load_recent_tracks(self.app.get_spotify_client())
            else:
                print("Failed to find RecentlyPlayedView")

class SpotifyView(Container):
    BINDINGS = [
        Binding("ctrl+r", "refresh", "Refresh"),
    ]

    def __init__(self):
        super().__init__()
        self.auth = SpotifyAuth(self.app.db)
        if self.auth.spotify_client:
            self.app.set_spotify_auth(self.auth)
            self.call_after_refresh = True
        else:
            self.call_after_refresh = False
    
    def on_mount(self) -> None:
        self._search_id = 0
        if self.call_after_refresh:
            library_section = self.query_one(LibrarySection)
            library_section.load_playlists(self.auth.spotify_client)
    
    def compose(self) -> ComposeResult:
        yield SpotifyPlayer()
        yield Container(
            Static(f"Spotify - {'Connected 🟢' if self.auth.spotify_client else 'Not Connected 🔴'}", id="status-bar-title", classes="status-item"),
            classes="status-bar"
        )
        yield Container(
            Container(
                Container(
                    LibrarySection(),
                    classes="playlists-section"
                ),
                Container(
                    Static("Instructions", classes="section-title"),
                    Static("CTRL+R: Refresh", classes="instruction-item"),
                    Static("Space: Play/Pause", classes="instruction-item"),
                    Button("Authenticate Spotify", id="auth-btn", variant="primary", classes="connect-spotify-button"),
                    classes="instructions-section"
                ),
                classes="sidebar"
            ),
            Container(
                MainContent(),
                classes="main-content"
            ),
            classes="spotify-container"
        )
    
    def on_button_pressed(self, event: Button.Pressed) -> None:
        if event.button.id == "auth-btn":
            self.notify("Starting Spotify authentication...")
            self.action_authenticate()
            self.app.set_spotify_auth(self.auth)
            event.stop()

    @work
    async def do_auth(self):
        self.auth.start_auth()
        while not SpotifyCallbackHandler.auth_code:
            await asyncio.sleep(1)
        try:
            token_info = self.auth.sp_oauth.get_access_token(SpotifyCallbackHandler.auth_code)
            if token_info:
                self.auth.spotify_client = spotipy.Spotify(auth=token_info["access_token"])
                library_section = self.query_one(LibrarySection)
                library_section.load_playlists(self.auth.spotify_client)
                
                main_content = self.query_one(MainContent)
                recently_played = main_content.query_one(RecentlyPlayedView)
                if recently_played:
                    recently_played.load_recent_tracks(self.auth.spotify_client)
                    
                self.notify("Successfully connected to Spotify!")
                status_bar = self.query_one("#status-bar-title")
                status_bar.update("Spotify - Connected 🟢")
            else:
                self.notify("Failed to authenticate with Spotify", severity="error")
                status_bar = self.query_one("#status-bar-title")
                status_bar.update("Spotify - Not Connected 🔴")
        except:
            self.notify("Failed to authenticate with Spotify", severity="error")
            status_bar = self.query_one("#status-bar-title")
            status_bar.update("Spotify - Not Connected 🔴")

    def action_authenticate(self):
        self.do_auth()

    def get_spotify_client(self):
        return self.auth.spotify_client

    def on_input_changed(self, event: Input.Changed) -> None:
        if not self.auth.spotify_client:
            return

        query = event.value.strip()
        if not query:
            try:
                results_container = self.query_one(SearchResult).query_one("#search-results-container")
                if results_container:
                    results_container.remove_children()
            except:
                pass
            return

        self._search_id += 1
        self.run_search(query, self._search_id)

    @work
    async def run_search(self, query: str, search_id: int) -> None:
        await asyncio.sleep(0.5)

        if search_id != self._search_id:
            return
        spotify_client = self.app.get_spotify_client()
        if not spotify_client:
            return
        worker = get_current_worker()

        try:
            results = spotify_client.search(q=query, type='track,playlist', limit=10)

            if worker.is_cancelled:
                return

            results_container = self.query_one("#search-results-container")
            if results_container:
                results_container.remove_children()
                if results.get('tracks', {}).get('items'):
                    for track in results['tracks']['items']:
                        if worker.is_cancelled:
                            return
                        results_container.mount(SearchResult(
                            track['name'],
                            track['id'],
                            'track',
                            ", ".join(artist['name'] for artist in track['artists'])
                        ))
                if results.get('playlists', {}).get('items'):
                    for playlist in results['playlists']['items']:
                        if worker.is_cancelled:
                            return
                        results_container.mount(SearchResult(
                            playlist['name'],
                            playlist['id'],
                            'playlist'
                        ))
        except Exception as e:
            if not worker.is_cancelled and search_id == self._search_id:
                print(f"Search error: {str(e)}")

    def on_playlist_item_selected(self, message: PlaylistItem.Selected) -> None:
        playlist_view = self.query_one(PlaylistView)
        playlist_view.load_playlist(self.auth.spotify_client, message.playlist_id)

    @work
    async def on_search_result_selected(self, message: SearchResult.Selected) -> None:
        if message.result_type == 'playlist':
            playlist_view = self.query_one(PlaylistView)
            playlist_view.load_playlist(self.auth.spotify_client, message.result_id)
        elif message.result_type == 'track':
            try:
                devices = self.auth.spotify_client.devices()
                if not devices['devices']:
                    self.notify("No Spotify devices found. Please open Spotify on any device.", severity="error")
                    return
                        
                active_device = next((d for d in devices['devices'] if d['is_active']), devices['devices'][0])
                
                playlist_view = self.query_one(PlaylistView)
                current_playlist_id = playlist_view.current_playlist_id
                
                if current_playlist_id and current_playlist_id != "liked_songs":
                    if message.position is not None:
                        self.auth.spotify_client.start_playback(
                            device_id=active_device['id'],
                            context_uri=f"spotify:playlist:{current_playlist_id}",
                            offset={"position": message.position}
                        )
                        await asyncio.sleep(0.5)
                        current = self.auth.spotify_client.current_playback()
                        if current and current.get('item'):
                            track = current['item']
                            artist_names = ", ".join(artist['name'] for artist in track['artists'])
                            self.notify(f"Now playing - {track['name']} by {artist_names}")
                    else:
                        playlist = self.auth.spotify_client.playlist(current_playlist_id)
                        track_uris = [track['track']['uri'] for track in playlist['tracks']['items'] if track['track']]
                        
                        try:
                            track_index = track_uris.index(f"spotify:track:{message.result_id}")
                            self.auth.spotify_client.start_playback(
                                device_id=active_device['id'],
                                context_uri=f"spotify:playlist:{current_playlist_id}",
                                offset={"position": track_index}
                            )
                            await asyncio.sleep(0.5)
                            current = self.auth.spotify_client.current_playback()
                            if current and current.get('item'):
                                track = current['item']
                                artist_names = ", ".join(artist['name'] for artist in track['artists'])
                                self.notify(f"Now playing - {track['name']} by {artist_names}")
                        except ValueError:
                            self.auth.spotify_client.start_playback(
                                device_id=active_device['id'],
                                uris=[f"spotify:track:{message.result_id}"]
                            )
                            await asyncio.sleep(0.5)
                            current = self.auth.spotify_client.current_playback()
                            if current and current.get('item'):
                                track = current['item']
                                artist_names = ", ".join(artist['name'] for artist in track['artists'])
                                self.notify(f"Now playing - {track['name']} by {artist_names}")
                else:
                    self.auth.spotify_client.start_playback(
                        device_id=active_device['id'],
                        uris=[f"spotify:track:{message.result_id}"]
                    )
                    await asyncio.sleep(0.5)
                    current = self.auth.spotify_client.current_playback()
                    if current and current.get('item'):
                        track = current['item']
                        artist_names = ", ".join(artist['name'] for artist in track['artists'])
                        self.notify(f"Now playing - {track['name']} by {artist_names}")
            except Exception as e:
                self.notify(f"Playback error: {str(e)}", severity="error") 

    def action_focus_search(self) -> None:
        search_input = self.query_one("Input")
        search_input.focus()

    def action_refresh(self) -> None:
        if self.auth.spotify_client:
            library_section = self.query_one(LibrarySection)
            library_section.load_playlists(self.auth.spotify_client)
            
            playlist_view = self.query_one(PlaylistView)
            if playlist_view.current_playlist_id:
                playlist_view.load_playlist(self.auth.spotify_client, playlist_view.current_playlist_id)
                
            main_content = self.query_one(MainContent)
            recently_played = main_content.query_one(RecentlyPlayedView)
            if recently_played:
                recently_played.load_recent_tracks(self.auth.spotify_client)