import os
from typing import Dict, Any, List
from datetime import datetime

from sqlalchemy.orm import Session

from .spotify_client import SpotifyClient
from .reccobeats_client import ReccoBeatsClient
from .narrative_service import NarrativeService


class PlaylistService:
    def __init__(
        self,
        db: Session,
        spotify_client: SpotifyClient,
        recco_client: ReccoBeatsClient,
        narrative_service: NarrativeService,
    ) -> None:
        self.db = db
        self.spotify = spotify_client
        self.recco = recco_client
        self.narrative = narrative_service

    async def ensure_playlists_exist(self, user_id: str) -> Dict[str, str]:
        """Check/create playlists. Returns {six_hour_id, daily_id}."""
        from app.models import PlaylistConfig

        six_hour_config = (
            self.db.query(PlaylistConfig)
            .filter(PlaylistConfig.key == "six_hour")
            .first()
        )
        daily_config = (
            self.db.query(PlaylistConfig).filter(PlaylistConfig.key == "daily").first()
        )

        six_hour_id = six_hour_config.spotify_id if six_hour_config else None
        daily_id = daily_config.spotify_id if daily_config else None

        if not six_hour_id:
            six_hour_id = os.getenv("SIX_HOUR_PLAYLIST_ID")
            if not six_hour_id:
                six_hour_data = await self.spotify.create_playlist(
                    user_id=user_id,
                    name="Short and Sweet",
                    description="AI-curated 6-hour playlists based on your listening habits",
                )
                six_hour_id = str(six_hour_data["id"])

            self._save_playlist_config("six_hour", six_hour_id, "Short and Sweet")

        if not daily_id:
            daily_id = os.getenv("DAILY_PLAYLIST_ID")
            if not daily_id:
                daily_data = await self.spotify.create_playlist(
                    user_id=user_id,
                    name="Proof of Commitment",
                    description="Your daily 24-hour mix showing your music journey",
                )
                daily_id = str(daily_data["id"])

            self._save_playlist_config("daily", daily_id, "Proof of Commitment")

        return {"six_hour_id": six_hour_id, "daily_id": daily_id}

    def _save_playlist_config(
        self,
        key: str,
        spotify_id: str,
        description: str = None,
        theme: str = None,
        composition: List[Dict[str, Any]] = None,
    ):
        from app.models import PlaylistConfig

        config = self.db.query(PlaylistConfig).filter(PlaylistConfig.key == key).first()
        if not config:
            config = PlaylistConfig(key=key, spotify_id=spotify_id)
            self.db.add(config)
        else:
            config.spotify_id = spotify_id

        if description:
            config.description = description
        if theme:
            config.current_theme = theme
        if composition:
            config.composition = composition

        config.last_updated = datetime.utcnow()
        self.db.commit()

    async def _hydrate_tracks(
        self, track_ids: List[str], sources: Dict[str, str]
    ) -> List[Dict[str, Any]]:
        """Fetch full track details for a list of IDs."""
        from app.models import Track

        db_tracks = self.db.query(Track).filter(Track.id.in_(track_ids)).all()
        track_map = {t.id: t for t in db_tracks}

        missing_ids = [tid for tid in track_ids if tid not in track_map]

        if missing_ids:
            spotify_tracks = await self.spotify.get_tracks(missing_ids)
            for st in spotify_tracks:
                if not st:
                    continue
                track_map[st["id"]] = {
                    "id": st["id"],
                    "name": st["name"],
                    "artist": ", ".join([a["name"] for a in st["artists"]]),
                    "image": st["album"]["images"][0]["url"]
                    if st["album"]["images"]
                    else None,
                    "uri": st["uri"],
                }

        result = []
        for tid in track_ids:
            track = track_map.get(tid)
            if not track:
                continue

            if hasattr(track, "name") and not isinstance(track, dict):
                track_data = {
                    "id": track.id,
                    "name": track.name,
                    "artist": track.artist,
                    "image": track.image_url,
                    "uri": f"spotify:track:{track.id}",
                }
            else:
                track_data = track

            track_data["source"] = sources.get(tid, "unknown")
            result.append(track_data)

        return result

    async def curate_six_hour_playlist(
        self, period_start: datetime, period_end: datetime
    ) -> Dict[str, Any]:
        """Generate 6-hour playlist (15 curated + 15 recommendations)."""
        from app.models import Track, PlayHistory
        from app.services.stats_service import StatsService
        from sqlalchemy import func

        stats = StatsService(self.db)
        data = stats.generate_full_report(period_start, period_end)

        top_tracks_period = [t["id"] for t in data["volume"].get("top_tracks", [])][:15]

        if len(top_tracks_period) < 5:
            fallback_tracks = (
                self.db.query(Track.id, func.count(PlayHistory.id).label("cnt"))
                .join(PlayHistory, Track.id == PlayHistory.track_id)
                .group_by(Track.id)
                .order_by(func.count(PlayHistory.id).desc())
                .limit(15)
                .all()
            )
            top_tracks_period = [tid for tid, _ in fallback_tracks]

        listening_data = {
            "peak_hour": data["time_habits"].get("peak_hour", 12),
            "avg_energy": data["vibe"].get("avg_energy", 0.5),
            "avg_valence": data["vibe"].get("avg_valence", 0.5),
            "total_plays": data["volume"].get("total_plays", 0),
            "top_artists": data["volume"].get("top_artists", [])[:10],
        }

        theme_result = self.narrative.generate_playlist_theme(listening_data)

        curated_details = []
        for tid in top_tracks_period:
            track_obj = self.db.query(Track).filter(Track.id == tid).first()
            if track_obj:
                curated_details.append(
                    {
                        "id": str(track_obj.id),
                        "energy": track_obj.energy,
                        "source": "history",
                    }
                )

        rec_details = []
        seed_ids = top_tracks_period[:5] if top_tracks_period else []
        if seed_ids:
            raw_recs = await self.recco.get_recommendations(
                seed_ids=seed_ids,
                size=15,
            )
            for r in raw_recs:
                rec_id = str(r.get("spotify_id") or r.get("id"))
                if rec_id:
                    rec_details.append(
                        {
                            "id": rec_id,
                            "energy": r.get("energy"),
                            "source": "recommendation",
                        }
                    )

        all_candidates = curated_details[:15] + rec_details[:15]
        optimized_tracks = self._optimize_playlist_flow(all_candidates)

        final_track_ids = [t["id"] for t in optimized_tracks]
        sources = {t["id"]: t["source"] for t in optimized_tracks}

        # Hydrate for persistence/display
        full_tracks = await self._hydrate_tracks(final_track_ids, sources)

        playlist_id = None
        from app.models import PlaylistConfig

        config = (
            self.db.query(PlaylistConfig)
            .filter(PlaylistConfig.key == "six_hour")
            .first()
        )
        if config:
            playlist_id = config.spotify_id

        if not playlist_id:
            playlist_id = os.getenv("SIX_HOUR_PLAYLIST_ID")

        if playlist_id:
            theme_name = f"Short and Sweet - {theme_result['theme_name']}"
            desc = f"{theme_result['description']}\n\nCurated: {len(curated_details)} tracks + {len(rec_details)} recommendations"

            await self.spotify.update_playlist_details(
                playlist_id=playlist_id,
                name=theme_name,
                description=desc,
            )
            await self.spotify.replace_playlist_tracks(
                playlist_id=playlist_id,
                track_uris=[f"spotify:track:{tid}" for tid in final_track_ids],
            )

            self._save_playlist_config(
                "six_hour",
                playlist_id,
                description=desc,
                theme=theme_result["theme_name"],
                composition=full_tracks,
            )

        return {
            "playlist_id": playlist_id,
            "theme_name": theme_result["theme_name"],
            "description": theme_result["description"],
            "track_count": len(final_track_ids),
            "sources": sources,
            "composition": full_tracks,
            "curated_count": len(curated_details),
            "rec_count": len(rec_details),
            "refreshed_at": datetime.utcnow().isoformat(),
        }

    async def curate_daily_playlist(
        self, period_start: datetime, period_end: datetime
    ) -> Dict[str, Any]:
        """Generate 24-hour playlist (30 favorites + 20 discoveries)."""
        from app.models import Track
        from app.services.stats_service import StatsService

        stats = StatsService(self.db)
        data = stats.generate_full_report(period_start, period_end)

        top_all_time_ids = self._get_top_all_time_tracks(limit=30)
        recent_tracks_ids = [track["id"] for track in data["volume"]["top_tracks"][:20]]

        favorites_details = []
        for tid in top_all_time_ids:
            track_obj = self.db.query(Track).filter(Track.id == tid).first()
            if track_obj:
                favorites_details.append(
                    {
                        "id": str(track_obj.id),
                        "energy": track_obj.energy,
                        "source": "favorite_all_time",
                    }
                )

        discovery_details = []
        for tid in recent_tracks_ids:
            track_obj = self.db.query(Track).filter(Track.id == tid).first()
            if track_obj:
                discovery_details.append(
                    {
                        "id": str(track_obj.id),
                        "energy": track_obj.energy,
                        "source": "recent_discovery",
                    }
                )

        all_candidates = favorites_details + discovery_details
        optimized_tracks = self._optimize_playlist_flow(all_candidates)

        final_track_ids = [t["id"] for t in optimized_tracks]
        sources = {t["id"]: t["source"] for t in optimized_tracks}

        # Hydrate for persistence/display
        full_tracks = await self._hydrate_tracks(final_track_ids, sources)

        playlist_id = None
        from app.models import PlaylistConfig

        config = (
            self.db.query(PlaylistConfig).filter(PlaylistConfig.key == "daily").first()
        )
        if config:
            playlist_id = config.spotify_id

        if not playlist_id:
            playlist_id = os.getenv("DAILY_PLAYLIST_ID")

        theme_name = f"Proof of Commitment - {datetime.utcnow().date().isoformat()}"
        if playlist_id:
            desc = (
                f"{theme_name} reflects the past 24 hours plus your all-time devotion."
            )
            await self.spotify.update_playlist_details(
                playlist_id=playlist_id,
                name=theme_name,
                description=desc,
            )
            await self.spotify.replace_playlist_tracks(
                playlist_id=playlist_id,
                track_uris=[f"spotify:track:{tid}" for tid in final_track_ids],
            )

            self._save_playlist_config(
                "daily",
                playlist_id,
                description=desc,
                theme=theme_name,
                composition=full_tracks,
            )

        return {
            "playlist_id": playlist_id,
            "theme_name": theme_name,
            "description": "Daily mix refreshed with your favorites and discoveries.",
            "track_count": len(final_track_ids),
            "sources": sources,
            "composition": full_tracks,
            "favorites_count": len(favorites_details),
            "recent_discoveries_count": len(discovery_details),
            "refreshed_at": datetime.utcnow().isoformat(),
        }

    def _get_top_all_time_tracks(self, limit: int = 30) -> List[str]:
        """Get top tracks by play count from all-time history."""
        from app.models import PlayHistory, Track
        from sqlalchemy import func

        result = (
            self.db.query(Track.id, func.count(PlayHistory.id).label("play_count"))
            .join(PlayHistory, Track.id == PlayHistory.track_id)
            .group_by(Track.id)
            .order_by(func.count(PlayHistory.id).desc())
            .limit(limit)
            .all()
        )

        return [track_id for track_id, _ in result]

    def _optimize_playlist_flow(
        self, tracks: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """
        Sort tracks to create a smooth flow (Energy Ramp).
        Strategy: Sort by energy (Low -> High -> Medium).
        """
        if not any("energy" in t for t in tracks):
            return tracks

        for t in tracks:
            if "energy" not in t or t["energy"] is None:
                t["energy"] = 0.5

        sorted_tracks = sorted(tracks, key=lambda x: x["energy"])

        n = len(sorted_tracks)
        low_end = int(n * 0.3)
        high_start = int(n * 0.7)

        low_energy = sorted_tracks[:low_end]
        medium_energy = sorted_tracks[low_end:high_start]
        high_energy = sorted_tracks[high_start:]

        return low_energy + high_energy + medium_energy
