"""Incrementally scan the local music library into the DB.

Only files whose mtime/size changed are re-read (tracked in file_state); files
that disappeared are pruned. Embedded ISRC and MusicBrainz recording MBID are
read from tags, so tagged tracks get real identifiers with no network.
"""
import logging
import os
from datetime import datetime

from tqdm import tqdm

from Env import local_library_path
from src.db.database import session_scope
from src.db.models import FileState, MB_FOUND, SOURCE_LOCAL, Track
from src.ingest.tags import read_track_tags
from src.match.normalize import norm_key

log = logging.getLogger("musicindexer.local")

MUSIC_EXTS = (".mp3", ".flac", ".m4a", ".ogg", ".opus", ".wav", ".aac")


def _iter_files(root):
    for dirpath, _dirs, names in os.walk(root):
        for name in names:
            if os.path.splitext(name)[1].lower() in MUSIC_EXTS:
                yield os.path.join(dirpath, name)


def scan_local(root=None):
    root = str(root or local_library_path)
    if not os.path.isdir(root):
        log.warning("local library path not found: %s — skipping", root)
        return {"new": 0, "updated": 0, "unchanged": 0, "removed": 0}

    files = list(_iter_files(root))
    counts = {"new": 0, "updated": 0, "unchanged": 0, "removed": 0}

    with session_scope() as session:
        states = {fs.path: fs for fs in session.query(FileState).all()}
        tracks = {t.source_id: t for t in
                  session.query(Track).filter_by(source=SOURCE_LOCAL).all()}
        seen = set()

        for path in tqdm(files, desc="scan local", unit="file"):
            seen.add(path)
            stat = os.stat(path)
            state = states.get(path)
            if state and state.mtime == stat.st_mtime and state.size == stat.st_size:
                counts["unchanged"] += 1
                continue

            tags = read_track_tags(path)
            if not tags or not (tags["title"] or tags["artist"]):
                continue

            fields = dict(
                title=tags["title"], artist=tags["artist"], artists=tags["artists"],
                album=tags["album"], duration_ms=tags["duration_ms"],
                isrc=tags["isrc"], local_path=path,
                norm_key=norm_key(tags["artist"] or "", tags["title"] or ""),
            )
            track = tracks.get(path)
            if track is None:
                track = Track(source=SOURCE_LOCAL, source_id=path, **fields)
                session.add(track)
                tracks[path] = track
                counts["new"] += 1
            else:
                for key, value in fields.items():
                    setattr(track, key, value)
                counts["updated"] += 1
            # Tagged files come with their recording MBID -> resolved for free.
            if tags["mbid"]:
                track.mbid = tags["mbid"]
                track.mbid_status = MB_FOUND
                track.mbid_source = "tag"

            if state is None:
                session.add(FileState(path=path, mtime=stat.st_mtime,
                                      size=stat.st_size, indexed_at=datetime.utcnow()))
            else:
                state.mtime, state.size, state.indexed_at = (
                    stat.st_mtime, stat.st_size, datetime.utcnow())

        # Prune rows whose file no longer exists.
        for path, track in tracks.items():
            if path not in seen and os.path.isabs(path):
                session.delete(track)
                counts["removed"] += 1
        for path, state in states.items():
            if path not in seen:
                session.delete(state)

    log.info("local: %(new)d new, %(updated)d updated, %(unchanged)d unchanged, "
             "%(removed)d removed (%(total)d files scanned)",
             {**counts, "total": len(files)})
    return counts
