"""Liked-artist multi-source matching — lifted from web_server.py.

Both function bodies are byte-identical to the originals. The
``spotify_client`` proxy + ``_get_*_client`` shims let the bodies resolve
their original names without any modification.
"""
import logging
import time

from config.settings import config_manager
from core.metadata.registry import (
    get_deezer_client,
    get_discogs_client,
    get_itunes_client,
    get_musicbrainz_client,
    get_spotify_client,
)

logger = logging.getLogger(__name__)


def _get_itunes_client():
    """Mirror of web_server._get_itunes_client — delegates to registry."""
    return get_itunes_client()


def _get_deezer_client():
    """Mirror of web_server._get_deezer_client — delegates to registry."""
    return get_deezer_client()


def _get_discogs_client(token=None):
    """Mirror of web_server._get_discogs_client — delegates to registry."""
    return get_discogs_client(token)


def _get_musicbrainz_client():
    """Mirror of web_server._get_musicbrainz_client — delegates to registry."""
    return get_musicbrainz_client()


class _SpotifyClientProxy:
    """Resolves the global Spotify client lazily so a Spotify re-auth that
    rebinds the cached client in core.metadata.registry is visible to the
    lifted bodies."""

    def __getattr__(self, name):
        client = get_spotify_client()
        if client is None:
            raise AttributeError(name)
        return getattr(client, name)

    def __bool__(self):
        return get_spotify_client() is not None


spotify_client = _SpotifyClientProxy()


def _match_liked_artists_to_all_sources(database, profile_id: int):
    """Match pending liked artists to ALL metadata sources (Spotify, iTunes, Deezer, Discogs).
    Uses the same matching pattern as the watchlist scanner: DB-first, then API search
    with fuzzy name matching. Stores all resolved IDs so source switching works instantly."""
    pending = database.get_liked_artists_pending_match(profile_id, limit=200)
    if not pending:
        return

    # Source → column mapping
    source_cols = {
        'spotify': 'spotify_artist_id',
        'itunes': 'itunes_artist_id',
        'deezer': 'deezer_artist_id',
        'discogs': 'discogs_artist_id',
        'musicbrainz': 'musicbrainz_artist_id',
    }
    id_cols = list(source_cols.values())

    # Reject known placeholder images and local server paths
    _placeholder_hashes = {'2a96cbd8b46e442fc41c2b86b821562f'}
    def _valid_image(url):
        if not url or not url.strip():
            return None
        if any(ph in url for ph in _placeholder_hashes):
            return None
        # Reject local media server paths (Plex/Jellyfin) — not loadable in browser
        if url.startswith('/') or url.startswith('\\'):
            return None
        if not url.startswith('http'):
            return None
        return url

    # Build search clients for each source
    from core.deezer_client import DeezerClient
    search_clients = {}
    if spotify_client and spotify_client.is_spotify_authenticated():
        search_clients['spotify'] = spotify_client
    try:
        search_clients['itunes'] = _get_itunes_client()
    except Exception as e:
        logger.debug("itunes client init failed: %s", e)
    try:
        search_clients['deezer'] = _get_deezer_client()
    except Exception as e:
        logger.debug("deezer client init failed: %s", e)
    try:
        dc = _get_discogs_client()
        # Only use Discogs if token is configured
        from config.settings import config_manager as _cm
        if _cm.get('discogs.token', ''):
            search_clients['discogs'] = dc
    except Exception as e:
        logger.debug("discogs client init failed: %s", e)
    try:
        search_clients['musicbrainz'] = _get_musicbrainz_client()
    except Exception as e:
        logger.debug("musicbrainz client init failed: %s", e)

    # Reuse watchlist scanner's fuzzy matching logic
    from core.watchlist_scanner import WatchlistScanner
    _normalize = WatchlistScanner._normalize_artist_name

    def _best_match(results, artist_name):
        """Pick best match from search results using name similarity (same as watchlist scanner)."""
        if not results:
            return None
        # Exact normalized match
        for r in results:
            if _normalize(r.name) == _normalize(artist_name):
                return r
        # Fuzzy scoring
        best = None
        best_sim = 0
        for r in results:
            # Simple normalized comparison
            n1 = _normalize(artist_name)
            n2 = _normalize(r.name)
            if n1 == n2:
                return r
            # Levenshtein-style similarity
            max_len = max(len(n1), len(n2))
            if max_len == 0:
                continue
            distance = sum(1 for a, b in zip(n1, n2, strict=False) if a != b) + abs(len(n1) - len(n2))
            sim = (max_len - distance) / max_len
            if sim > best_sim:
                best_sim = sim
                best = r
        if best and best_sim >= 0.85:
            return best
        return None

    api_calls = 0
    matched = 0

    for entry in pending:
        name = entry['artist_name']
        pool_id = entry['id']
        harvested_ids = {}
        best_image = None

        # Pre-load existing IDs from the entry itself
        for col in id_cols:
            if entry.get(col):
                harvested_ids[col] = entry[col]

        # --- DB STRATEGIES (free, no API calls) ---

        # 1. Library artists table
        try:
            conn = database._get_connection()
            cursor = conn.cursor()
            cursor.execute("SELECT * FROM artists WHERE name = ? COLLATE NOCASE LIMIT 1", (name,))
            row = cursor.fetchone()
            if row:
                r = dict(row)
                for col in id_cols:
                    if r.get(col) and col not in harvested_ids:
                        harvested_ids[col] = str(r[col])
                if _valid_image(r.get('thumb_url')):
                    best_image = r['thumb_url']
        except Exception as e:
            logger.debug("library artist lookup failed: %s", e)

        # 2. Watchlist artists
        try:
            conn = database._get_connection()
            cursor = conn.cursor()
            cursor.execute(
                "SELECT * FROM watchlist_artists WHERE artist_name = ? COLLATE NOCASE AND profile_id = ? LIMIT 1",
                (name, profile_id)
            )
            row = cursor.fetchone()
            if row:
                wl = dict(row)
                for col in id_cols:
                    if wl.get(col) and col not in harvested_ids:
                        harvested_ids[col] = str(wl[col])
                if _valid_image(wl.get('image_url')) and not best_image:
                    best_image = wl['image_url']
        except Exception as e:
            logger.debug("watchlist artist lookup failed: %s", e)

        # 3. Metadata cache (all sources)
        try:
            conn = database._get_connection()
            cursor = conn.cursor()
            cursor.execute(
                "SELECT entity_id, source, image_url FROM metadata_cache_entities WHERE entity_type = 'artist' AND name = ? COLLATE NOCASE",
                (name,)
            )
            for row in cursor.fetchall():
                col = source_cols.get(row['source'])
                if col and col not in harvested_ids:
                    harvested_ids[col] = row['entity_id']
                if _valid_image(row['image_url']) and not best_image:
                    best_image = row['image_url']
        except Exception as e:
            logger.debug("metadata cache lookup failed: %s", e)

        # --- API STRATEGIES (search each missing source) ---
        # Same pattern as watchlist scanner's _backfill_missing_ids
        for source, col in source_cols.items():
            if col in harvested_ids:
                continue  # Already have this source's ID
            client = search_clients.get(source)
            if not client:
                continue
            if api_calls >= 200:  # Hard cap per refresh cycle
                break
            try:
                results = client.search_artists(name, limit=5)
                best = _best_match(results, name)
                if best:
                    harvested_ids[col] = best.id
                    if hasattr(best, 'image_url') and _valid_image(best.image_url) and not best_image:
                        best_image = best.image_url
                api_calls += 1
                time.sleep(0.4)  # Rate limit breathing room
            except Exception as e:
                logger.debug(f"[Your Artists] {source} search failed for '{name}': {e}")
                api_calls += 1

        # Save all harvested IDs
        if harvested_ids:
            # Determine best active source/ID — prefer Spotify, then iTunes, Deezer, Discogs
            resolved_source = None
            resolved_id = None
            for src in ('spotify', 'itunes', 'deezer', 'discogs', 'musicbrainz'):
                col = source_cols[src]
                if col in harvested_ids:
                    resolved_source = src
                    resolved_id = harvested_ids[col]
                    break

            database.update_liked_artist_match(
                pool_id, active_source=resolved_source, active_source_id=resolved_id,
                image_url=best_image, all_ids=harvested_ids
            )
            matched += 1

    database.sync_liked_artists_watchlist_flags(profile_id)
    logger.info(f"[Your Artists] Matched {matched}/{len(pending)} artists to {len(search_clients)} sources ({api_calls} API calls)")

    # Image backfill: fetch images for matched artists that have IDs but no image
    _backfill_liked_artist_images(database, profile_id, search_clients)


def _backfill_liked_artist_images(database, profile_id: int, search_clients: dict):
    """Fetch images for matched artists missing artwork using their stored source IDs."""
    try:
        conn = database._get_connection()
        cursor = conn.cursor()
        cursor.execute("""
            SELECT id, artist_name, spotify_artist_id, itunes_artist_id, deezer_artist_id
            FROM liked_artists_pool
            WHERE profile_id = ? AND match_status = 'matched'
              AND (image_url IS NULL OR image_url = ''
                   OR image_url LIKE '%2a96cbd8b46e442fc41c2b86b821562f%'
                   OR image_url NOT LIKE 'http%')
            LIMIT 100
        """, (profile_id,))
        rows = cursor.fetchall()
        if not rows:
            return

        logger.info(f"[Your Artists] Backfilling images for {len(rows)} artists...")
        filled = 0

        for row in rows:
            r = dict(row)
            image_url = None

            # Try Spotify artist lookup (has best images)
            if r.get('spotify_artist_id') and 'spotify' in search_clients:
                try:
                    sp = search_clients['spotify']
                    if hasattr(sp, 'sp') and sp.sp:
                        artist_data = sp.sp.artist(r['spotify_artist_id'])
                        if artist_data and artist_data.get('images'):
                            image_url = artist_data['images'][0]['url']
                except Exception as e:
                    logger.debug("spotify artist image fetch failed: %s", e)

            # Try Deezer (direct image URL from ID)
            if not image_url and r.get('deezer_artist_id'):
                image_url = f"https://api.deezer.com/artist/{r['deezer_artist_id']}/image?size=big"

            if image_url:
                try:
                    cursor2 = conn.cursor()
                    cursor2.execute(
                        "UPDATE liked_artists_pool SET image_url = ? WHERE id = ?",
                        (image_url, r['id'])
                    )
                    filled += 1
                except Exception as e:
                    logger.debug("liked artist image update failed: %s", e)
                time.sleep(0.3)

        conn.commit()
        if filled:
            logger.info(f"[Your Artists] Backfilled {filled}/{len(rows)} artist images")
    except Exception as e:
        logger.debug(f"[Your Artists] Image backfill error: {e}")
