"""
sync.py — Push/pull synkronisering mellem lokal app og server.

POST /sync/push  — send lokal data op til server
GET  /sync/pull  — hent server-data ned til app
"""
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from typing import Optional

from app.core.database import get_db
from app.core.security import get_current_user
from app.models import (
    User, Song, Dance, DanceLevel, Project, ProjectSong,
    PlaylistShare, CommunityDance, CommunityDanceAlt,
)

router = APIRouter(prefix="/sync", tags=["sync"])


# ── Schemas ───────────────────────────────────────────────────────────────────

class SongData(BaseModel):
    local_id:     str
    title:        str
    artist:       str = ""
    album:        str = ""
    bpm:          int = 0
    duration_sec: int = 0
    file_format:  str = ""
    mbid:         str = ""
    acoustid:     str = ""

class DanceData(BaseModel):
    name:          str
    level_name:    str = ""
    choreographer: str = ""
    video_url:     str = ""
    stepsheet_url: str = ""
    notes:         str = ""

class SongDanceData(BaseModel):
    song_local_id:  str
    dance_name:     str
    level_name:     str = ""
    dance_order:    int = 1

class SongAltDanceData(BaseModel):
    song_local_id:  str
    dance_name:     str
    level_name:     str = ""
    note:           str = ""

class PlaylistSongData(BaseModel):
    song_local_id:  str
    song_title:     str = ""
    song_artist:    str = ""
    position:       int
    status:         str = "pending"
    is_workshop:    bool = False
    dance_override: str = ""

class PlaylistData(BaseModel):
    local_id:    str
    name:        str
    description: str = ""
    tags:        str = ""
    visibility:  str = "private"
    songs:       list[PlaylistSongData] = []

class PushPayload(BaseModel):
    songs:              list[SongData] = []
    dances:             list[DanceData] = []
    song_dances:        list[SongDanceData] = []
    song_alts:          list[SongAltDanceData] = []
    playlists:          list[PlaylistData] = []
    deleted_playlists:  list[str] = []   # server-IDs (api_project_id) på slettede playlister


# ── Push ──────────────────────────────────────────────────────────────────────

@router.post("/push")
def push(
    payload: PushPayload,
    db: Session = Depends(get_db),
    me: User = Depends(get_current_user),
):
    """Upload lokal data til server. Returnerer server-IDs."""
    song_id_map = {}    # local_id → server Song.id
    dance_id_map = {}   # "name|level" → server Dance.id
    level_map = {}      # level_name → DanceLevel.id

    # ── Dans-niveauer ─────────────────────────────────────────────────────────
    for lvl in db.query(DanceLevel).all():
        level_map[lvl.name.lower()] = lvl.id

    # ── Sange ─────────────────────────────────────────────────────────────────
    for s in payload.songs:
        if not s.title:
            continue
        # Match 1: MBID — sikrest
        existing = None
        if s.mbid:
            existing = db.query(Song).filter_by(mbid=s.mbid).first()
        # Match 2: titel+artist globalt
        if not existing:
            existing = db.query(Song).filter(
                Song.title == s.title,
                Song.artist == s.artist,
            ).first()
        if existing:
            song_id_map[s.local_id] = existing.id
            # Opdater BPM og MBID hvis de mangler
            if s.bpm and not existing.bpm:
                existing.bpm = s.bpm
            if s.mbid and not existing.mbid:
                existing.mbid = s.mbid
            if s.acoustid and not existing.acoustid:
                existing.acoustid = s.acoustid
        else:
            song = Song(
                owner_id=me.id,
                title=s.title, artist=s.artist, album=s.album,
                bpm=s.bpm, duration_sec=s.duration_sec,
                file_format=s.file_format,
                mbid=s.mbid or None,
                acoustid=s.acoustid or None,
            )
            db.add(song)
            db.flush()
            song_id_map[s.local_id] = song.id

    # ── Danse ──────────────────────────────────────────────────────────────────
    for d in payload.dances:
        level_id = level_map.get(d.level_name.lower()) if d.level_name else None
        key = f"{d.name.lower()}|{level_id}"
        existing = db.query(Dance).filter_by(name=d.name, level_id=level_id).first()
        if existing:
            # Opdater info hvis den har ny data
            if d.choreographer:  existing.choreographer = d.choreographer
            if d.video_url:      existing.video_url = d.video_url
            if d.stepsheet_url:  existing.stepsheet_url = d.stepsheet_url
            if d.notes:          existing.notes = d.notes
            dance_id_map[key] = existing.id
        else:
            dance = Dance(
                name=d.name, level_id=level_id,
                choreographer=d.choreographer, video_url=d.video_url,
                stepsheet_url=d.stepsheet_url, notes=d.notes,
            )
            db.add(dance)
            db.flush()
            dance_id_map[key] = dance.id

    # ── Sang-dans tags (brugerens egne) ───────────────────────────────────────
    from app.models import SongDance, SongAltDance
    # ── Sang-dans tags ────────────────────────────────────────────────────────
    from app.models import SongDance, SongAltDance
    import sqlalchemy as _sa

    for sd in payload.song_dances:
        song_id = song_id_map.get(sd.song_local_id)
        if not song_id:
            continue
        level_id = level_map.get(sd.level_name.lower()) if sd.level_name else None
        key = f"{sd.dance_name.lower()}|{level_id}"
        dance_id = dance_id_map.get(key)
        if not dance_id:
            continue
        db.execute(_sa.text(
            "INSERT IGNORE INTO song_dances (id, song_id, dance_id, dance_order) "
            "VALUES (:id, :song_id, :dance_id, :dance_order)"
        ), {
            "id":         str(__import__("uuid").uuid4()),
            "song_id":    song_id,
            "dance_id":   dance_id,
            "dance_order": sd.dance_order,
        })

    for sa in payload.song_alts:
        song_id = song_id_map.get(sa.song_local_id)
        if not song_id:
            continue
        level_id = level_map.get(sa.level_name.lower()) if sa.level_name else None
        key = f"{sa.dance_name.lower()}|{level_id}"
        dance_id = dance_id_map.get(key)
        if not dance_id:
            continue
        db.execute(_sa.text(
            "INSERT IGNORE INTO song_alt_dances (id, song_id, dance_id, note) "
            "VALUES (:id, :song_id, :dance_id, :note)"
        ), {
            "id":      str(__import__("uuid").uuid4()),
            "song_id": song_id,
            "dance_id": dance_id,
            "note":    sa.note or "",
        })

    # ── Playlister ────────────────────────────────────────────────────────────
    playlist_id_map = {}
    for pl in payload.playlists:
        existing = db.query(Project).filter_by(
            owner_id=me.id, name=pl.name
        ).first()
        if existing:
            existing.description = pl.description
            existing.visibility  = pl.visibility
            # Opdater kun sange hvis push faktisk har sange med
            if pl.songs:
                db.query(ProjectSong).filter_by(project_id=existing.id).delete()
            project = existing
        else:
            project = Project(
                owner_id=me.id, name=pl.name,
                description=pl.description, visibility=pl.visibility,
            )
            db.add(project)
            db.flush()
        playlist_id_map[pl.local_id] = project.id

        for ps in pl.songs:
            # Prøv først via song_id_map (lokal ID)
            song_id = song_id_map.get(ps.song_local_id)
            # Fallback: match på titel+artist
            if not song_id and ps.song_title:
                existing_song = db.query(Song).filter_by(
                    title=ps.song_title, artist=ps.song_artist
                ).first()
                if existing_song:
                    song_id = existing_song.id
            if not song_id:
                continue
            proj_song = ProjectSong(
                project_id=project.id, song_id=song_id,
                position=ps.position, status=ps.status,
                is_workshop=ps.is_workshop,
                dance_override=ps.dance_override,
            )
            db.add(proj_song)

    # ── Slet playlister der er fjernet lokalt ─────────────────────────────────
    # Klienten sender api_project_id (= server Project.id) som strings
    for project_id in payload.deleted_playlists:
        proj = db.query(Project).filter_by(id=project_id, owner_id=me.id).first()
        if proj:
            db.query(ProjectSong).filter_by(project_id=proj.id).delete()
            db.delete(proj)

    db.commit()

    return {
        "status": "ok",
        "songs_synced":     len(song_id_map),
        "playlists_synced": len(playlist_id_map)
        #"song_id_map":      song_id_map,
        #"playlist_id_map":  playlist_id_map,
    }


# ── Pull ──────────────────────────────────────────────────────────────────────

@router.get("/pull")
def pull(
    db: Session = Depends(get_db),
    me: User = Depends(get_current_user),
):
    """Hent server-data til lokal app."""

    # Dans-niveauer
    levels = [
        {"id": l.id, "name": l.name, "sort_order": l.sort_order}
        for l in db.query(DanceLevel).order_by(DanceLevel.sort_order).all()
    ]

    # Danse med info
    dances = [
        {
            "name": d.name,
            "level_id": d.level_id,
            "choreographer": d.choreographer,
            "video_url": d.video_url,
            "stepsheet_url": d.stepsheet_url,
            "notes": d.notes,
            "use_count": d.use_count,
        }
        for d in db.query(Dance).order_by(Dance.use_count.desc()).limit(500).all()
    ]

    # Community dans-tags (populære)
    community = []
    for cd in db.query(CommunityDance).limit(1000).all():
        community.append({
            "song_title":  cd.song_title,
            "song_artist": cd.song_artist,
            "dance_id":    cd.dance_id,
        })

    # Delte playlister (read-only — kun ejeren kan redigere)
    shared_ids = set()
    for s in db.query(PlaylistShare).filter(
        (PlaylistShare.shared_with_id == me.id) |
        (PlaylistShare.invited_email == me.email)
    ).all():
        shared_ids.add(s.project_id)

    shared = []
    for p in db.query(Project).filter(Project.id.in_(shared_ids)).all():
        if p.owner_id == me.id:
            continue  # Egne lister håndteres separat
        owner = db.query(User).filter_by(id=p.owner_id).first()
        songs_out = []
        for ps in p.project_songs:
            song = db.query(Song).filter_by(id=ps.song_id).first()
            if not song:
                continue
            songs_out.append({
                "title":         song.title,
                "artist":        song.artist,
                "position":      ps.position,
                "status":        ps.status,
                "is_workshop":   ps.is_workshop,
                "dance_override": ps.dance_override or "",
            })
        shared.append({
            "server_id":  p.id,
            "name":       p.name,
            "owner":      owner.username if owner else "?",
            "songs":      sorted(songs_out, key=lambda x: x["position"]),
        })

    # Egne playlister
    my_playlists = []
    all_projects = db.query(Project).filter_by(owner_id=me.id).all()
    import logging
    logging.getLogger(__name__).info(f"Pull: fandt {len(all_projects)} projekter for {me.id}")
    for p in all_projects:
        songs_out = []
        for ps in p.project_songs:
            song = db.query(Song).filter_by(id=ps.song_id).first()
            if not song:
                continue
            songs_out.append({
                "title":         song.title,
                "artist":        song.artist,
                "position":      ps.position,
                "status":        ps.status,
                "is_workshop":   ps.is_workshop,
                "dance_override": ps.dance_override or "",
            })
        my_playlists.append({
            "server_id":   p.id,
            "name":        p.name,
            "description": p.description or "",
            "songs":       sorted(songs_out, key=lambda x: x["position"]),
        })

    # Brugerens egne dans-tags
    from app.models import SongDance, SongAltDance
    song_tags = []
    for sd in db.query(SongDance).join(Song).filter(Song.owner_id == me.id).all():
        dance = db.query(Dance).filter_by(id=sd.dance_id).first()
        if not dance:
            continue
        level = db.query(DanceLevel).filter_by(id=dance.level_id).first() if dance.level_id else None
        song_tags.append({
            "song_title":  sd.song.title,
            "song_artist": sd.song.artist,
            "dance_name":  dance.name,
            "level_name":  level.name if level else "",
            "dance_order": sd.dance_order,
        })

    return {
        "levels":       levels,
        "dances":       dances,
        "community":    community,
        "shared":       shared,
        "my_playlists": my_playlists,
        "song_tags":    song_tags,
    }