#!/usr/bin/env python3
"""
Worker thread for background tasks.
"""

import os
import logging
from typing import Optional

from PyQt6.QtCore import QThread, pyqtSignal

from track_info import TrackInfo
from audio_converter import AudioConverter
from metadata_handler import MetadataHandler
from ipod_device import IPodDevice
from library_cache import invalidate_cache_for_path

logger = logging.getLogger(__name__)

AUDIO_EXTS = {'.m4a', '.mp3', '.aac'}
SOURCE_EXTS = {'.flac', '.wav', '.ogg', '.opus', '.wma', '.ape', '.aiff', '.alac'}


class WorkerThread(QThread):
    """Worker thread for background tasks"""

    _processed_fwids: set[str] = set()
    _auto_synced_fwids: set[str] = set()
    _pending_commit_fwids: set[str] = set()

    progress_signal = pyqtSignal(int, str)
    finished_signal = pyqtSignal(bool, str, object)

    def __init__(self, task_type: str, parent=None, **kwargs):
        super().__init__(parent)
        self.task_type = task_type
        self.kwargs = kwargs
        self.is_running = True

    def run(self):
        try:
            handlers = {
                "convert": self._run_convert,
                "transfer": self._run_transfer,
                "detect_devices": self._run_detect_devices,
                "mount_and_load": self._run_mount_and_load,
                "export_ipod": self._run_export_ipod,
                "delete_tracks": self._run_delete_tracks,
                "remove_duplicates": self._run_remove_duplicates,
                "sync_metadata": self._run_sync_metadata,
                "create_playlist": self._run_create_playlist,
                "auto_sync_playcounts": self._run_auto_sync_playcounts,
            }
            handler = handlers.get(self.task_type)
            if handler:
                handler()
            else:
                raise ValueError(f"Unknown task type: {self.task_type}")
        except Exception as e:
            logger.exception(f"Error in worker thread: {e}")
            self.finished_signal.emit(False, str(e), None)

    def _run_convert(self):
        tracks = self.kwargs.get("tracks", [])
        local_files = self.kwargs.get("local_files", [])
        output_dir = self.kwargs.get("output_dir", "converted")
        fmt = self.kwargs.get("format", "mp3")
        quality = self.kwargs.get("quality", 256)

        valid_tracks = [
            t for t in tracks
            if hasattr(t, 'download_path') and t.download_path and os.path.exists(t.download_path)
        ]

        metadata_handler = MetadataHandler()
        for f in local_files:
            if os.path.exists(f):
                track = metadata_handler.extract_tags(f)
                if track:
                    track.download_path = f
                    valid_tracks.append(track)

        if not valid_tracks:
            self.finished_signal.emit(False, "No valid tracks to convert", None)
            return

        self.progress_signal.emit(0, f"Initializing conversion of {len(valid_tracks)} tracks")

        converter = AudioConverter(output_dir=output_dir)
        metadata_handler = MetadataHandler()

        converted_tracks = []
        for i, track in enumerate(valid_tracks):
            if not self.is_running:
                break

            progress = int(((i + 1) / len(valid_tracks)) * 100)
            self.progress_signal.emit(progress, f"Converting {track.title}...")

            try:
                output_path = converter.convert_to_ipod_format(
                    track, track.download_path, fmt, quality
                )
                if output_path:
                    metadata_handler.process_file(output_path, track)
                    converted_tracks.append((track, output_path))
            except Exception as e:
                logger.error(f"Error converting track {track.title}: {e}")
                continue

        if not converted_tracks:
            self.finished_signal.emit(False, "Failed to convert any tracks", None)
            return

        self.finished_signal.emit(True, f"Converted {len(converted_tracks)} tracks", converted_tracks)

    def _run_transfer(self):
        tracks = self.kwargs.get("tracks", [])
        mount_point = self.kwargs.get("mount_point")

        if not tracks:
            self.finished_signal.emit(False, "No tracks to transfer", None)
            return

        if not mount_point:
            self.finished_signal.emit(False, "No iPod device mounted", None)
            return

        self.progress_signal.emit(0, f"Initializing transfer of {len(tracks)} tracks")

        ipod = IPodDevice(mount_point=mount_point)
        device_info = ipod.get_device_info()
        free_space = device_info.get("free_space", 0)

        total_size = sum(os.path.getsize(path) for _, path in tracks if os.path.exists(path))

        if total_size > free_space:
            self.finished_signal.emit(
                False,
                f"Not enough space on iPod. Need {total_size / 1024**2:.1f} MB, "
                f"but only {free_space / 1024**2:.1f} MB available",
                None
            )
            return

        transferred_tracks = []
        for i, (track, path) in enumerate(tracks):
            if not self.is_running:
                break

            progress = int(((i + 1) / len(tracks)) * 100)
            self.progress_signal.emit(progress, f"Transferring {track.title}...")

            if os.path.exists(path):
                try:
                    success = ipod.transfer_file(path)
                    if success:
                        transferred_tracks.append((track, path))
                except Exception as e:
                    logger.error(f"Error transferring track {track.title}: {e}")
                    continue

        self.finished_signal.emit(
            True, f"Transferred {len(transferred_tracks)} tracks to iPod", transferred_tracks
        )

    def _run_detect_devices(self):
        self.progress_signal.emit(0, "Detecting iPod devices...")

        ipod = IPodDevice()
        devices = ipod.detect_devices()

        if not devices:
            self.finished_signal.emit(False, "No iPod devices found", [])
            return

        self.finished_signal.emit(True, f"Found {len(devices)} iPod devices", devices)

    def _run_mount_and_load(self):
        import shutil

        device = self.kwargs.get("device", {})
        mount_point = self.kwargs.get("mount_point")
        already_mounted = self.kwargs.get("already_mounted", False)

        if not already_mounted:
            device_id = device.get("id")
            detected_mount = device.get("mount_point")
            ipod = IPodDevice()
            mount_point = ipod.mount_device(device_id, detected_mount=detected_mount)
            if not mount_point:
                self.finished_signal.emit(False, "Failed to mount iPod", None)
                return
            device["mount_point"] = mount_point
            device["mounted"] = True

        from ipod_nano7_db import Nano7Database

        usage = shutil.disk_usage(mount_point)
        device_info = {
            "mount_point": mount_point,
            "total_space": usage.total,
            "free_space": usage.free,
            "used_space": usage.used,
        }

        db = Nano7Database(mount_point)
        tracks = db.get_all_tracks()
        device_info["track_count"] = len(tracks)

        self.progress_signal.emit(60, "Syncing play counts from iPod...")

        from library_cache import get_library_cache, save_library_cache
        cache = get_library_cache()

        ipod_base, _ipod_delta = db.read_play_stats()
        fwid = db.firewire_id.hex()
        WorkerThread._processed_fwids.add(fwid)

        # ── build content_hash → iPod location map ──────────────────────
        hash_to_location: dict[str, str] = {}
        for t in tracks:
            ch = t.get("content_hash")
            if not ch:
                continue
            fpath = t.get("file_path", "")
            if not fpath:
                continue
            rel = os.path.relpath(fpath, os.path.join(db.mountpoint, "iPod_Control", "Music"))
            loc = f":iPod_Control:Music:{rel.replace(os.sep, ':')}"
            hash_to_location[ch] = loc

        # ── merge into local cache ──────────────────────────────────────
        updated_cache = 0
        for file_path, entry in cache.iter_entries():
            if not os.path.exists(file_path):
                continue
            ch = entry.get("content_hash", "")
            if not ch or ch not in hash_to_location:
                continue
            loc = hash_to_location[ch]
            bs = ipod_base.get(loc, {})

            new_play_count = max(
                entry.get("play_count", 0), bs.get("play_count", 0),
            )
            new_skip_count = max(
                entry.get("skip_count", 0), bs.get("skip_count", 0),
            )
            new_rating = bs.get("rating", 0) or entry.get("rating", 0)
            new_last_played = max(
                entry.get("last_played", 0), bs.get("last_played", 0),
            )

            cache.update_play_stats(
                file_path,
                play_count=new_play_count,
                rating=new_rating,
                last_played=new_last_played,
                skip_count=new_skip_count,
            )
            updated_cache += 1

        if updated_cache:
            save_library_cache()
            logger.info("Updated play counts for %d local tracks from iPod", updated_cache)

        # ── deferred iTunesDB commit on stable USB ──────────────────
        if fwid in WorkerThread._pending_commit_fwids and os.access(mount_point, os.W_OK):
            if db.sync_itunescdb():
                WorkerThread._pending_commit_fwids.discard(fwid)
                logger.info("Deferred iTunesDB commit for %s complete", fwid)
            else:
                logger.debug("Deferred commit for %s failed (read-only filesystem)", fwid)

        self.progress_signal.emit(80, "Loading complete")

        result = {
            "device": device,
            "mount_point": mount_point,
            "device_info": device_info,
            "tracks": tracks,
        }
        self.finished_signal.emit(
            True, f"Loaded {len(tracks)} tracks from iPod", result
        )

    def _run_auto_sync_playcounts(self):
        mount_point = self.kwargs.get("mount_point")
        if not mount_point:
            self.finished_signal.emit(False, "No mount point for auto-sync", None)
            return

        ipod_ctrl = os.path.join(mount_point, "iPod_Control")
        if not os.path.exists(ipod_ctrl):
            self.finished_signal.emit(True, "Auto-sync skipped (device gone)", None)
            return

        from ipod_nano7_db import Nano7Database
        db = Nano7Database(mount_point)
        fwid = db.firewire_id.hex()

        if fwid in WorkerThread._auto_synced_fwids:
            self.finished_signal.emit(True, "Auto-sync already done", None)
            return

        ipod_base, ipod_delta = db.read_play_stats()
        if not ipod_base and not ipod_delta:
            import time
            time.sleep(3)
            ipod_base, ipod_delta = db.read_play_stats()
        if not ipod_base and not ipod_delta:
            ipod_delta = db.read_play_counts_delta()
        if not ipod_delta:
            self.finished_signal.emit(True, "Auto-sync: no delta", None)
            return

        from library_cache import get_library_cache, save_library_cache
        cache = get_library_cache()
        stored = cache.get_last_synced_delta(fwid)

        effective: dict[str, dict[str, int]] = {}
        for loc, dd in ipod_delta.items():
            sd = stored.get(loc, {})
            eff_pc = max(0, dd.get("play_count", 0) - sd.get("play_count", 0))
            eff_sc = max(0, dd.get("skip_count", 0) - sd.get("skip_count", 0))
            if eff_pc > 0 or eff_sc > 0:
                effective[loc] = {"play_count": eff_pc, "skip_count": eff_sc}
            dd_rating = dd.get("rating", 0)
            if dd_rating > 0:
                effective.setdefault(loc, {})["rating"] = dd_rating

        cache.set_last_synced_delta(fwid, ipod_delta)

        if effective:
            tracks = db.get_all_tracks()
            hash_to_loc: dict[str, str] = {}
            loc_to_hash: dict[str, str] = {}
            for t in tracks:
                ch = t.get("content_hash", "")
                fpath = t.get("file_path", "")
                if ch and fpath:
                    rel = os.path.relpath(
                        fpath, os.path.join(db.mountpoint, "iPod_Control", "Music"),
                    )
                    loc = f":iPod_Control:Music:{rel.replace(os.sep, ':')}"
                    loc_to_hash[loc] = ch
                    hash_to_loc[ch] = loc

            self.progress_signal.emit(40, "Merging play counts...")
            updated = 0
            for file_path, entry in cache.iter_entries():
                ch = entry.get("content_hash", "")
                if ch not in hash_to_loc:
                    continue
                loc = hash_to_loc[ch]
                eff = effective.get(loc)
                if not eff:
                    continue
                bs = ipod_base.get(loc, {})
                new_pc = max(entry.get("play_count", 0), bs.get("play_count", 0)) + eff.get(
                    "play_count", 0,
                )
                new_sc = max(entry.get("skip_count", 0), bs.get("skip_count", 0)) + eff.get(
                    "skip_count", 0,
                )
                new_rt = eff.get("rating", 0) or bs.get("rating", 0) or entry.get("rating", 0)
                new_lp = max(entry.get("last_played", 0), bs.get("last_played", 0))
                cache.update_play_stats(
                    file_path,
                    play_count=new_pc,
                    rating=new_rt,
                    last_played=new_lp,
                    skip_count=new_sc,
                )
                updated += 1

            if updated:
                save_library_cache()
                logger.info("Auto-sync: applied %d effective deltas", updated)

        self.progress_signal.emit(100, "Auto-sync complete")
        WorkerThread._pending_commit_fwids.add(fwid)
        WorkerThread._auto_synced_fwids.add(fwid)
        self.finished_signal.emit(True, "Auto-sync complete", mount_point)

    def _run_export_ipod(self):
        tracks = self.kwargs.get("tracks", [])
        dest_dir = self.kwargs.get("dest_dir", "")
        mount_point = self.kwargs.get("mount_point")

        if not tracks or not dest_dir or not mount_point:
            self.finished_signal.emit(False, "Missing parameters for export", None)
            return

        self.progress_signal.emit(0, f"Exporting {len(tracks)} track(s)...")

        ipod = IPodDevice(mount_point=mount_point)

        def progress(p, s):
            self.progress_signal.emit(p, s)

        exported = ipod.export_tracks(tracks, dest_dir, progress_callback=progress)
        self.finished_signal.emit(True, f"Exported {len(exported)} track(s)", exported)

    def _run_delete_tracks(self):
        pids = self.kwargs.get("pids", [])
        mount_point = self.kwargs.get("mount_point")

        if not pids or not mount_point:
            self.finished_signal.emit(False, "No tracks to delete or device not mounted", None)
            return

        from ipod_nano7_db import Nano7Database
        db = Nano7Database(mount_point)

        def progress(p, s):
            self.progress_signal.emit(p, s)

        removed = db.delete_tracks(pids, progress_callback=progress)
        self.finished_signal.emit(True, f"Removed {removed} track(s)", removed)

    def _run_remove_duplicates(self):
        mount_point = self.kwargs.get("mount_point")

        if not mount_point:
            self.finished_signal.emit(False, "No iPod device mounted", None)
            return

        from ipod_nano7_db import Nano7Database
        db = Nano7Database(mount_point)

        def progress(p, s):
            self.progress_signal.emit(p, s)

        self.progress_signal.emit(0, "Scanning for duplicates...")
        removed = db.remove_duplicates(progress_callback=progress)
        self.finished_signal.emit(True, f"Removed {len(removed)} duplicate(s)", removed)

    def _run_sync_metadata(self):
        """Update metadata and artwork for tracks already on iPod (batch)."""
        sync_batch = self.kwargs.get("sync_batch", [])
        mount_point = self.kwargs.get("mount_point")

        if not sync_batch or not mount_point:
            self.finished_signal.emit(False, "Missing parameters for sync", None)
            return

        from ipod_nano7_db import Nano7Database
        db = Nano7Database(mount_point)
        total = len(sync_batch)
        synced = 0
        errors = []

        for i, item in enumerate(sync_batch):
            if not self.is_running:
                break

            self.progress_signal.emit(
                int((i / total) * 100),
                f"Syncing metadata: {item['new_metadata'].get('title', '?')}...",
            )

            try:
                success = db.update_track_metadata(
                    pid=item["ipod_pid"],
                    new_metadata=item["new_metadata"],
                    local_source_path=item.get("local_path"),
                )
                if success:
                    synced += 1
                else:
                    errors.append(item['new_metadata'].get('title', '?'))
            except Exception as e:
                logger.exception("Metadata sync failed for track: %s", e)
                errors.append(f"{item['new_metadata'].get('title', '?')}: {e}")

        msg = f"Synced metadata for {synced}/{total} track(s)"
        if errors:
            msg += f" ({len(errors)} error(s))"
        self.progress_signal.emit(100, msg)
        self.finished_signal.emit(synced > 0, msg, {"synced": synced, "errors": errors})

    def _run_create_playlist(self):
        mount_point = self.kwargs.get("mount_point")
        name = self.kwargs.get("playlist_name", "")
        track_paths = self.kwargs.get("track_paths", [])

        if not mount_point or not name:
            self.finished_signal.emit(False, "Missing parameters for playlist creation", None)
            return

        from ipod_nano7_db import Nano7Database
        db = Nano7Database(mount_point)

        self.progress_signal.emit(0, f"Creating playlist '{name}' on iPod...")
        success = db.create_playlist(name, track_paths)

        if success:
            self.finished_signal.emit(True, f"Playlist '{name}' created on iPod", None)
        else:
            self.finished_signal.emit(False, f"Failed to create playlist '{name}'", None)

    def stop(self):
        self.is_running = False
        self.wait()
