import os
import uuid
import logging
from fastapi import FastAPI, BackgroundTasks, Request, Form
from fastapi.responses import HTMLResponse, JSONResponse
from pydantic import BaseModel
import requests
from curl_cffi import requests as cf_requests

from tasks import run_download_task

# Configure Logger
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("musicseerr-main")

app = FastAPI(title="MusicSeerr Ingestion Pipeline", version="1.0.0")

import json

JOBS_FILE = "/remote-music/.musicseerr_jobs.json"

from tasks import JOBS_WRITE_LOCK

def save_jobs_db(jobs_dict):
    with JOBS_WRITE_LOCK:
        try:
            temp_file = f"{JOBS_FILE}.{uuid.uuid4()}.tmp"
            with open(temp_file, "w") as f:
                json.dump(jobs_dict, f, indent=2)
            os.replace(temp_file, JOBS_FILE)
        except Exception as e:
            logger.error(f"Failed to save jobs database: {e}")


def load_jobs_db():
    if os.path.exists(JOBS_FILE):
        try:
            with open(JOBS_FILE, "r") as f:
                return json.load(f)
        except Exception as e:
            logger.error(f"Failed to load jobs database: {e}")
    return {}

# Load jobs database
JOBS = load_jobs_db()

# Read configurations from Environment Variables
PROXY_URL = os.getenv("GLUETUN_PROXY_URL", "http://gluetun-svc:8888")
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")

class SubmitRequest(BaseModel):
    query: str

def send_telegram_message(chat_id: int, text: str):
    """
    Sends a message back to Telegram. Outgoing traffic uses the normal cluster network.
    """
    if not TELEGRAM_BOT_TOKEN or not chat_id:
        return
    url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
    payload = {"chat_id": chat_id, "text": text}
    try:
        requests.post(url, json=payload, timeout=10)
    except Exception as e:
        logger.error(f"Failed to send Telegram message to chat {chat_id}: {e}")


@app.get("/health")
def health_check():
    return {"status": "healthy", "proxy": PROXY_URL}


@app.get("/jobs")
def get_jobs():
    """
    Returns the list of all jobs, sorted by timestamp.
    """
    return JSONResponse(content=list(JOBS.values()))


@app.get("/status/{task_id}")
def get_job_status(task_id: str):
    """
    Returns the status of a specific job.
    """
    if task_id not in JOBS:
        return JSONResponse(status_code=404, content={"error": "Job not found"})
    return JSONResponse(content=JOBS[task_id])


@app.post("/submit")
def submit_job(req: SubmitRequest, background_tasks: BackgroundTasks):
    """
    API endpoint to submit download jobs via JSON.
    """
    task_id = str(uuid.uuid4())
    query = req.query.strip()
    
    if not query:
        return JSONResponse(status_code=400, content={"error": "Query cannot be empty"})
        
    JOBS[task_id] = {
        "id": task_id,
        "query": query,
        "status": "pending",
        "priority_used": None,
        "files": [],
        "error": None,
        "chat_id": None
    }
    save_jobs_db(JOBS)
    
    background_tasks.add_task(run_download_task, task_id, query, PROXY_URL, JOBS)
    
    return {"task_id": task_id, "status": "pending"}


@app.post("/telegram-webhook")
async def telegram_webhook(request: Request, background_tasks: BackgroundTasks):
    """
    Endpoint for Telegram bot webhooks.
    """
    try:
        payload = await request.json()
        logger.info(f"Received Telegram Webhook Payload: {payload}")
        
        message = payload.get("message", {})
        chat = message.get("chat", {})
        chat_id = chat.get("id")
        text = message.get("text", "").strip()
        
        if not text or not chat_id:
            return {"status": "ignored"}
            
        # Ignore commands like /start unless they have arguments
        if text.startswith("/start"):
            parts = text.split(" ", 1)
            if len(parts) > 1:
                text = parts[1].strip()
            else:
                send_telegram_message(
                    chat_id, 
                    "Welcome to MusicSeerr! 🎵\nSend me a song name, Spotify URL, YouTube URL, or Tubidy link, and I'll ingest it into the Navidrome library!"
                )
                return {"status": "welcomed"}
                
        task_id = str(uuid.uuid4())
        JOBS[task_id] = {
            "id": task_id,
            "query": text,
            "status": "pending",
            "priority_used": None,
            "files": [],
            "error": None,
            "chat_id": chat_id
        }
        save_jobs_db(JOBS)
        
        # Pass chat_id to the background task (we will update run_download_task to support this)
        background_tasks.add_task(run_download_task, task_id, text, PROXY_URL, JOBS, chat_id)
        
        send_telegram_message(chat_id, f"📥 Queued: '{text}'\nTask ID: {task_id}")
        
    except Exception as e:
        logger.error(f"Error handling Telegram webhook: {e}")
        
    return {"status": "processed"}


@app.get("/", response_class=HTMLResponse)
def index_page():
    """
    Serves a highly-styled, responsive dark-mode portal.
    """
    html_content = """
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>MusicSeerr - Music Ingestion Portal</title>
        <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet">
        <style>
            :root {
                --bg-main: #0B0F19;
                --bg-card: #151D30;
                --bg-input: #1F2A45;
                --accent-primary: #8B5CF6;
                --accent-secondary: #EC4899;
                --text-main: #F3F4F6;
                --text-muted: #9CA3AF;
                --success: #10B981;
                --warning: #F59E0B;
                --error: #EF4444;
                --info: #3B82F6;
            }
            * {
                box-sizing: border-box;
                margin: 0;
                padding: 0;
                font-family: 'Outfit', sans-serif;
            }
            body {
                background: linear-gradient(135deg, var(--bg-main) 0%, #080B13 100%);
                color: var(--text-main);
                min-height: 100vh;
                display: flex;
                flex-direction: column;
                align-items: center;
                padding: 2rem 1rem;
            }
            header {
                text-align: center;
                margin-bottom: 2.5rem;
                max-width: 600px;
            }
            header h1 {
                font-size: 3rem;
                font-weight: 800;
                background: linear-gradient(to right, var(--accent-primary), var(--accent-secondary));
                -webkit-background-clip: text;
                -webkit-text-fill-color: transparent;
                margin-bottom: 0.5rem;
            }
            header p {
                color: var(--text-muted);
                font-size: 1.1rem;
            }
            .container {
                width: 100%;
                max-width: 800px;
                display: flex;
                flex-direction: column;
                gap: 2rem;
            }
            .card {
                background: var(--bg-card);
                border: 1px solid rgba(255, 255, 255, 0.05);
                border-radius: 16px;
                padding: 2rem;
                box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
            }
            form {
                display: flex;
                flex-direction: column;
                gap: 1.2rem;
            }
            .form-group {
                display: flex;
                flex-direction: column;
                gap: 0.5rem;
            }
            label {
                font-weight: 600;
                color: var(--text-main);
            }
            input[type="text"] {
                width: 100%;
                padding: 1rem;
                border-radius: 10px;
                border: 1px solid rgba(255, 255, 255, 0.1);
                background-color: var(--bg-input);
                color: var(--text-main);
                font-size: 1rem;
                transition: all 0.3s ease;
            }
            input[type="text"]:focus {
                outline: none;
                border-color: var(--accent-primary);
                box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.3);
            }
            button {
                padding: 1rem;
                border: none;
                border-radius: 10px;
                background: linear-gradient(to right, var(--accent-primary), var(--accent-secondary));
                color: white;
                font-size: 1.1rem;
                font-weight: 600;
                cursor: pointer;
                transition: all 0.3s ease;
                box-shadow: 0 4px 14px 0 rgba(139, 92, 246, 0.4);
            }
            button:hover {
                transform: translateY(-2px);
                box-shadow: 0 6px 20px 0 rgba(139, 92, 246, 0.6);
            }
            button:active {
                transform: translateY(0);
            }
            .jobs-section h2 {
                font-size: 1.5rem;
                font-weight: 600;
                margin-bottom: 1rem;
                border-left: 4px solid var(--accent-primary);
                padding-left: 0.75rem;
            }
            .job-list {
                display: flex;
                flex-direction: column;
                gap: 1rem;
                max-height: 500px;
                overflow-y: auto;
            }
            .job-item {
                background: var(--bg-input);
                border-radius: 10px;
                padding: 1.2rem;
                display: flex;
                justify-content: space-between;
                align-items: center;
                gap: 1rem;
                border-left: 4px solid var(--text-muted);
                transition: all 0.3s ease;
            }
            .job-item.pending { border-left-color: var(--info); }
            .job-item.downloading { border-left-color: var(--warning); }
            .job-item.completed { border-left-color: var(--success); }
            .job-item.failed { border-left-color: var(--error); }
            
            .job-info {
                display: flex;
                flex-direction: column;
                gap: 0.25rem;
                overflow: hidden;
            }
            .job-query {
                font-weight: 600;
                font-size: 1rem;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
            }
            .job-details {
                font-size: 0.8rem;
                color: var(--text-muted);
            }
            .badge {
                padding: 0.25rem 0.75rem;
                border-radius: 50px;
                font-size: 0.8rem;
                font-weight: 600;
                text-transform: uppercase;
            }
            .badge.pending { background: rgba(59, 130, 246, 0.2); color: var(--info); }
            .badge.downloading { background: rgba(245, 158, 11, 0.2); color: var(--warning); }
            .badge.completed { background: rgba(16, 185, 129, 0.2); color: var(--success); }
            .badge.failed { background: rgba(239, 68, 68, 0.2); color: var(--error); }
            
            .status-container {
                display: flex;
                gap: 2rem;
                margin-top: 1.5rem;
                font-size: 0.9rem;
                color: var(--text-muted);
                justify-content: center;
            }
            .status-item {
                display: flex;
                align-items: center;
                gap: 0.5rem;
            }
            .status-dot {
                width: 10px;
                height: 10px;
                border-radius: 50%;
                background-color: var(--success);
            }
        </style>
    </head>
    <body>
        <header>
            <h1>MusicSeerr 🎵</h1>
            <p>Sovereign Music Ingestion Portal for Navidrome</p>
        </header>
        
        <div class="container">
            <div class="card">
                <form id="ingestForm">
                    <div class="form-group">
                        <label for="query">Search Query or Media URL</label>
                        <input type="text" id="query" name="query" placeholder="Song Name, Spotify Link, YouTube Link, or Tubidy Link..." required>
                    </div>
                    <button type="submit">Ingest Audio</button>
                </form>
            </div>
            
            <div class="card jobs-section">
                <h2>Active Ingestion Jobs</h2>
                <div class="job-list" id="jobList">
                    <p style="color: var(--text-muted); text-align: center; padding: 2rem;">No active downloads. Submit a link above!</p>
                </div>
            </div>
        </div>
        
        <div class="status-container">
            <div class="status-item">
                <div class="status-dot"></div>
                <span>FastAPI Backend Active</span>
            </div>
            <div class="status-item">
                <div class="status-dot"></div>
                <span>VPN Proxy Bound</span>
            </div>
        </div>

        <script>
            const form = document.getElementById('ingestForm');
            const queryInput = document.getElementById('query');
            const jobList = document.getElementById('jobList');

            form.addEventListener('submit', async (e) => {
                e.preventDefault();
                const query = queryInput.value.trim();
                if (!query) return;

                try {
                    const response = await fetch('/submit', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({ query })
                    });
                    
                    if (response.ok) {
                        queryInput.value = '';
                        loadJobs();
                    } else {
                        alert('Failed to submit ingestion job.');
                    }
                } catch (err) {
                    console.error('Error submitting job:', err);
                }
            });

            async function loadJobs() {
                try {
                    const response = await fetch('/jobs');
                    if (response.ok) {
                        const jobs = await response.json();
                        if (jobs.length === 0) {
                            jobList.innerHTML = '<p style="color: var(--text-muted); text-align: center; padding: 2rem;">No active downloads. Submit a link above!</p>';
                            return;
                        }
                        
                        // Sort by completion / timestamp (newest first)
                        jobs.reverse();

                        jobList.innerHTML = jobs.map(job => {
                            let details = '';
                            if (job.status === 'completed' && job.files.length > 0) {
                                details = `Imported: ${job.files.join(', ')}`;
                            } else if (job.status === 'failed') {
                                details = `Error: ${job.error || 'Unknown error occurred'}`;
                            } else if (job.status === 'downloading') {
                                details = `Downloading via ${job.priority_used || 'pipeline'}...`;
                            } else {
                                details = 'Queued in background tasks';
                            }

                            return `
                                <div class="job-item ${job.status}">
                                    <div class="job-info">
                                        <div class="job-query">${escapeHtml(job.query)}</div>
                                        <div class="job-details">${escapeHtml(details)}</div>
                                    </div>
                                    <span class="badge ${job.status}">${job.status}</span>
                                </div>
                            `;
                        }).join('');
                    }
                } catch (err) {
                    console.error('Error loading jobs:', err);
                }
            }

            function escapeHtml(str) {
                return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
            }

            // Auto-refresh jobs every 5 seconds
            loadJobs();
            setInterval(loadJobs, 5000);
        </script>
    </body>
    </html>
    """
    return HTMLResponse(content=html_content)


def run_lastfm_auto_download(username: str):
    url = f"https://lfm.xiffy.nl/{username}/recommended"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
    }
    
    logger.info(f"Fetching Last.fm recommendations feed from {url}")
    response = None
    try:
        logger.info("Fetching Last.fm feed directly (no proxy)")
        response = cf_requests.get(url, headers=headers, impersonate="chrome", timeout=20)
        response.raise_for_status()
    except Exception as e:
        logger.warning(f"Failed to fetch Last.fm feed directly: {e}. Retrying with proxy...")
        if PROXY_URL:
            proxies = {"http": PROXY_URL, "https": PROXY_URL}
            try:
                response = cf_requests.get(url, headers=headers, proxies=proxies, impersonate="chrome", timeout=20)
                response.raise_for_status()
            except Exception as e2:
                logger.error(f"Failed to fetch Last.fm feed with proxy: {e2}")
                return
        else:
            return
        
    try:
        import xml.etree.ElementTree as ET
        root = ET.fromstring(response.content)
        items = root.findall(".//item")
    except Exception as e:
        logger.error(f"Failed to parse Last.fm recommendations XML: {e}")
        return
        
    logger.info(f"Found {len(items)} items in Last.fm feed.")
    
    # Import tasks dynamically to avoid circular import
    from tasks import run_download_task
    import re
    
    def is_track_already_downloaded(track_query: str) -> bool:
        remote_base = "/remote-music/music"
        if not os.path.isdir(remote_base):
            return False
        clean_query = re.sub(r'[^\w\s-]', '', track_query).lower()
        parts = track_query.split(" - ", 1)
        if len(parts) == 2:
            artist, title = parts[0].strip().lower(), parts[1].strip().lower()
            artist_clean = re.sub(r'[^\w\s-]', '', artist)
            title_clean = re.sub(r'[^\w\s-]', '', title)
        else:
            artist_clean = clean_query
            title_clean = clean_query
            
        for r, d, files in os.walk(remote_base):
            for file in files:
                if file.lower().endswith((".mp3", ".flac", ".m4a", ".ogg", ".wav", ".opus")):
                    file_lower = file.lower()
                    if len(parts) == 2:
                        if artist_clean in file_lower and title_clean in file_lower:
                            return True
                    else:
                        if clean_query in file_lower:
                            return True
        return False

    for item in items:
        title_el = item.find("title")
        if title_el is not None and title_el.text:
            track_query = title_el.text.strip()
            
            # Check if already queued
            already_queued = any(job.get("query") == f"[Last.fm Auto] {track_query}" for job in JOBS.values())
            if already_queued:
                continue
                
            if is_track_already_downloaded(track_query):
                logger.info(f"Skipping Last.fm auto-download for '{track_query}' (already exists).")
                continue
                
            logger.info(f"Queueing Last.fm recommendation: '{track_query}'")
            task_id = str(uuid.uuid4())
            JOBS[task_id] = {
                "id": task_id,
                "query": f"[Last.fm Auto] {track_query}",
                "status": "pending",
                "priority_used": None,
                "files": [],
                "error": None,
                "chat_id": None
            }
            save_jobs_db(JOBS)
            try:
                run_download_task(task_id, track_query, PROXY_URL, JOBS)
            except Exception as e:
                logger.error(f"Failed to process auto-download job {task_id}: {e}")


@app.post("/lastfm/trigger")
def trigger_lastfm_sync(background_tasks: BackgroundTasks):
    username = os.getenv("LASTFM_AUTO_DOWNLOAD_USER")
    if not username:
        return JSONResponse(status_code=400, content={"error": "Last.fm auto-download is not enabled (LASTFM_AUTO_DOWNLOAD_USER not configured)"})
    background_tasks.add_task(run_lastfm_auto_download, username)
    return {"status": "triggered", "user": username}


import asyncio

async def lastfm_scheduler():
    # Wait a short bit after startup before the first check
    await asyncio.sleep(30)
    while True:
        username = os.getenv("LASTFM_AUTO_DOWNLOAD_USER")
        if username:
            try:
                logger.info(f"Triggering scheduled Last.fm recommendations fetch for '{username}'...")
                loop = asyncio.get_event_loop()
                await loop.run_in_executor(None, run_lastfm_auto_download, username)
            except Exception as e:
                logger.error(f"Error in Last.fm scheduler task: {e}")
        
        interval_hours = float(os.getenv("LASTFM_AUTO_DOWNLOAD_INTERVAL_HOURS", "12"))
        await asyncio.sleep(interval_hours * 3600)


async def recover_jobs():
    # Wait for remote music directory mount to be fully ready
    logger.info("Starting recovery checks for interrupted/pending ingestion tasks...")
    await asyncio.sleep(10)
    
    global JOBS
    JOBS = load_jobs_db()
    
    recovered_count = 0
    for task_id, job in list(JOBS.items()):
        if job.get("status") in ("pending", "downloading"):
            logger.info(f"Recovering interrupted job {task_id}: '{job.get('query')}'")
            job["status"] = "pending"
            job["error"] = "Interrupted by server restart, resuming..."
            
            # Re-queue the task in a background thread executor using the event loop
            try:
                loop = asyncio.get_event_loop()
                loop.run_in_executor(
                    None, 
                    run_download_task, 
                    task_id, 
                    job.get("query"), 
                    PROXY_URL, 
                    JOBS, 
                    job.get("chat_id")
                )
                recovered_count += 1
            except Exception as e:
                logger.error(f"Failed to recover job {task_id}: {e}")
                
    if recovered_count > 0:
        save_jobs_db(JOBS)
        logger.info(f"Successfully recovered and resumed {recovered_count} tasks.")
    else:
        logger.info("No pending or interrupted ingestion tasks found to recover.")


@app.on_event("startup")
async def startup_event():
    asyncio.create_task(lastfm_scheduler())
    asyncio.create_task(recover_jobs())

# Trigger CI rebuild

