"""
LyricsService — Fetch lyrics from LRCLIB API with persistent SQLite caching (~/.subify/cache.db)
"""

import os
import json
import ssl
import sqlite3
import threading
import subprocess
from datetime import datetime
from urllib.request import urlopen, Request
from urllib.parse import urlencode
from urllib.error import URLError, HTTPError

CACHE_DIR = os.path.expanduser('~/.subify')
DB_PATH = os.path.join(CACHE_DIR, 'cache.db')


def _init_sqlite_db():
    os.makedirs(CACHE_DIR, exist_ok=True)
    with sqlite3.connect(DB_PATH) as conn:
        conn.execute('''
            CREATE TABLE IF NOT EXISTS lyrics_cache (
                track_key TEXT PRIMARY KEY,
                lrc_content TEXT,
                fetched_at TIMESTAMP
            )
        ''')
        conn.commit()


def _get_cached_lrc(track_key: str) -> str:
    try:
        if os.path.exists(DB_PATH):
            with sqlite3.connect(DB_PATH) as conn:
                cursor = conn.cursor()
                cursor.execute('SELECT lrc_content FROM lyrics_cache WHERE track_key = ?', (track_key,))
                row = cursor.fetchone()
                if row and row[0]:
                    return row[0]
    except Exception:
        pass
    return ""


def _save_cached_lrc(track_key: str, lrc_content: str):
    try:
        _init_sqlite_db()
        with sqlite3.connect(DB_PATH) as conn:
            conn.execute(
                'INSERT OR REPLACE INTO lyrics_cache (track_key, lrc_content, fetched_at) VALUES (?, ?, ?)',
                (track_key, lrc_content, datetime.now().isoformat())
            )
            conn.commit()
    except Exception:
        pass


def _create_ssl_context():
    try:
        ctx = ssl.create_default_context()
        if ctx.get_ca_certs():
            return ctx
    except Exception:
        pass
    return ssl._create_unverified_context()


class LyricsService:
    """Fetches lyrics from the LRCLIB API with multi-stage fallback and SQLite caching."""

    BASE_URL = 'https://lrclib.net/api'
    USER_AGENT = 'Subify/2.0 (https://github.com/sksarvesh007/Subify)'

    _instance = None

    @classmethod
    def shared(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def __init__(self):
        self._ssl_ctx = _create_ssl_context()
        _init_sqlite_db()

    def _make_request(self, url):
        try:
            req = Request(url)
            req.add_header('User-Agent', self.USER_AGENT)
            with urlopen(req, timeout=8, context=self._ssl_ctx) as resp:
                return json.loads(resp.read().decode('utf-8'))
        except Exception:
            pass

        try:
            result = subprocess.run(
                ['curl', '-s', '-H', f'User-Agent: {self.USER_AGENT}', url],
                capture_output=True, text=True, timeout=8
            )
            if result.returncode == 0 and result.stdout.strip():
                return json.loads(result.stdout)
        except Exception:
            pass

        return None

    def _clean_track_name(self, name):
        import re
        cleaned = re.sub(
            r'\s*[-–]\s*(\d{4}\s+)?'
            r'(Remaster(ed)?|Deluxe(\s+Edition)?|Bonus\s+Track|'
            r'Anniversary(\s+Edition)?|Expanded(\s+Edition)?|'
            r'Special(\s+Edition)?|Live|Mono|Stereo|Single(\s+Version)?|'
            r'Radio\s+Edit|Album\s+Version)'
            r'(\s*\d{0,4})?$',
            '', name, flags=re.IGNORECASE
        ).strip()
        cleaned = re.sub(r'[\(\[\{].*?(feat|ft|remaster|deluxe|version|edition|bonus|explicit).*?[\)\]\}]', '', cleaned, flags=re.IGNORECASE).strip()
        return cleaned if cleaned else name

    def fetch_lyrics(self, track_name, artist_name, album_name, duration, callback):
        def _worker():
            from lrc_parser import LRCParser

            cache_key = f"{track_name.lower()}|{artist_name.lower()}"

            # Check SQLite persistent cache
            cached_lrc = _get_cached_lrc(cache_key)
            if cached_lrc:
                callback(LRCParser.parse(cached_lrc))
                return

            raw_lrc = ""

            # Stage 1: Exact match
            raw_lrc = self._try_fetch_raw(track_name, artist_name, album_name, duration)

            # Stage 2: Cleaned title match
            if not raw_lrc:
                clean_name = self._clean_track_name(track_name)
                if clean_name != track_name:
                    raw_lrc = self._try_fetch_raw(clean_name, artist_name, album_name, duration)

            # Stage 3: Query search endpoint
            if not raw_lrc:
                clean_name = self._clean_track_name(track_name)
                raw_lrc = self._search_raw(clean_name, artist_name)

            lines = LRCParser.parse(raw_lrc) if raw_lrc else []

            if raw_lrc:
                _save_cached_lrc(cache_key, raw_lrc)

            callback(lines)

        thread = threading.Thread(target=_worker, daemon=True)
        thread.start()

    def _try_fetch_raw(self, track_name, artist_name, album_name, duration) -> str:
        params = urlencode({
            'track_name': track_name,
            'artist_name': artist_name,
            'album_name': album_name,
            'duration': str(int(duration))
        })
        url = f"{self.BASE_URL}/get?{params}"
        data = self._make_request(url)

        if data and isinstance(data, dict):
            synced = data.get('syncedLyrics')
            if synced:
                return synced
        return ""

    def _search_raw(self, track_name, artist_name) -> str:
        params = urlencode({
            'track_name': track_name,
            'artist_name': artist_name
        })
        url = f"{self.BASE_URL}/search?{params}"
        results = self._make_request(url)

        if results and isinstance(results, list):
            for result in results:
                synced = result.get('syncedLyrics')
                if synced:
                    return synced
        return ""
