from microdot import Microdot
from microdot.session import with_session
from models.sequence import Sequence
from models.profile import Profile
from models.transport import get_current_sender
from models.preset import Preset
from util.profile_bundle import export_sequence_bundle, import_sequence_bundle
import json

controller = Microdot()
sequences = Sequence()
profiles = Profile()
presets = Preset()


def get_current_profile_id(session=None):
    """Get the current active profile ID from session or fallback to first."""
    profile_list = profiles.list()
    session_profile = None
    if session is not None:
        session_profile = session.get("current_profile")
    if session_profile and session_profile in profile_list:
        return session_profile
    if profile_list:
        return profile_list[0]
    return None


@controller.get("")
@with_session
async def list_sequences(request, session):
    """List sequences for the current profile."""
    sequences.load()
    current_profile_id = get_current_profile_id(session)
    if not current_profile_id:
        return json.dumps({}), 200, {"Content-Type": "application/json"}
    scoped = {
        sid: sdata
        for sid, sdata in sequences.items()
        if isinstance(sdata, dict)
        and str(sdata.get("profile_id")) == str(current_profile_id)
    }
    return json.dumps(scoped), 200, {"Content-Type": "application/json"}


@controller.get("/<id>/export")
@with_session
async def export_sequence(request, session, id):
    """Export a sequence and referenced presets as a JSON bundle."""
    current_profile_id = get_current_profile_id(session)
    if not current_profile_id:
        return json.dumps({"error": "No profile available"}), 404, {"Content-Type": "application/json"}
    try:
        bundle = export_sequence_bundle(
            id,
            sequences,
            presets,
            profile_id=current_profile_id,
        )
        return json.dumps(bundle), 200, {"Content-Type": "application/json"}
    except ValueError as e:
        return json.dumps({"error": str(e)}), 404, {"Content-Type": "application/json"}


@controller.post("/import")
@with_session
async def import_sequence(request, session):
    """Import a sequence bundle into the current profile."""
    try:
        current_profile_id = get_current_profile_id(session)
        if not current_profile_id:
            return (
                json.dumps({"error": "No profile available"}),
                404,
                {"Content-Type": "application/json"},
            )
        body = request.json or {}
        bundle = body.get("bundle") if isinstance(body, dict) else body
        if not isinstance(bundle, dict):
            return (
                json.dumps({"error": "Expected JSON bundle"}),
                400,
                {"Content-Type": "application/json"},
            )
        new_id, seq_data = import_sequence_bundle(bundle, sequences, presets, current_profile_id)
        return (
            json.dumps({new_id: seq_data}),
            201,
            {"Content-Type": "application/json"},
        )
    except ValueError as e:
        return json.dumps({"error": str(e)}), 400, {"Content-Type": "application/json"}
    except Exception as e:
        return json.dumps({"error": str(e)}), 400, {"Content-Type": "application/json"}


@controller.get("/<id>")
@with_session
async def get_sequence(request, session, id):
    """Get a specific sequence by ID (current profile only)."""
    sequences.load()
    current_profile_id = get_current_profile_id(session)
    seq = sequences.read(id)
    if (
        seq
        and current_profile_id
        and str(seq.get("profile_id")) == str(current_profile_id)
    ):
        return json.dumps(seq), 200, {"Content-Type": "application/json"}
    return json.dumps({"error": "Sequence not found"}), 404


@controller.post("")
@with_session
async def create_sequence(request, session):
    """Create a new sequence for the current profile."""
    try:
        try:
            data = request.json or {}
        except Exception:
            return (
                json.dumps({"error": "Invalid JSON"}),
                400,
                {"Content-Type": "application/json"},
            )
        current_profile_id = get_current_profile_id(session)
        if not current_profile_id:
            return (
                json.dumps({"error": "No profile available"}),
                404,
                {"Content-Type": "application/json"},
            )
        sequence_id = sequences.create(current_profile_id)
        if not isinstance(data, dict):
            data = {}
        data = dict(data)
        data["profile_id"] = str(current_profile_id)
        if sequences.update(sequence_id, data):
            seq_data = sequences.read(sequence_id)
            return (
                json.dumps({sequence_id: seq_data}),
                201,
                {"Content-Type": "application/json"},
            )
        return (
            json.dumps({"error": "Failed to create sequence"}),
            400,
            {"Content-Type": "application/json"},
        )
    except Exception as e:
        return json.dumps({"error": str(e)}), 400, {"Content-Type": "application/json"}


@controller.put("/<id>")
@with_session
async def update_sequence(request, session, id):
    """Update an existing sequence (current profile only)."""
    try:
        current_profile_id = get_current_profile_id(session)
        seq = sequences.read(id)
        if not seq or str(seq.get("profile_id")) != str(current_profile_id):
            return json.dumps({"error": "Sequence not found"}), 404
        data = request.json
        if not isinstance(data, dict):
            return (
                json.dumps({"error": "Invalid JSON"}),
                400,
                {"Content-Type": "application/json"},
            )
        data = dict(data)
        data["profile_id"] = str(current_profile_id)
        if sequences.update(id, data):
            try:
                from util.sequence_playback import stop_if_playing_sequence

                stop_if_playing_sequence(str(id))
            except Exception:
                pass
            return json.dumps(sequences.read(id)), 200, {"Content-Type": "application/json"}
        return json.dumps({"error": "Sequence not found"}), 404
    except Exception as e:
        return json.dumps({"error": str(e)}), 400, {"Content-Type": "application/json"}


@controller.delete("/<id>")
@with_session
async def delete_sequence(request, session, id):
    """Delete a sequence (current profile only)."""
    current_profile_id = get_current_profile_id(session)
    seq = sequences.read(id)
    if not seq or str(seq.get("profile_id")) != str(current_profile_id):
        return json.dumps({"error": "Sequence not found"}), 404
    try:
        from util.sequence_playback import stop_if_playing_sequence

        stop_if_playing_sequence(str(id))
    except Exception:
        pass
    if sequences.delete(id):
        return (
            json.dumps({"message": "Sequence deleted successfully"}),
            200,
            {"Content-Type": "application/json"},
        )
    return json.dumps({"error": "Sequence not found"}), 404


@controller.post("/sync-phase")
@with_session
async def sync_sequence_beat_phase(request, session):
    """Align beat counters while a sequence is playing (body: {\"mode\": \"step\"|\"pass\"})."""
    _ = session
    try:
        data = request.json or {}
    except Exception:
        data = {}
    if not isinstance(data, dict):
        data = {}
    mode = data.get("mode") or data.get("align") or "step"
    try:
        from util.sequence_playback import sync_beat_phase

        if not await sync_beat_phase(str(mode)):
            return (
                json.dumps({"error": "No sequence is playing"}),
                409,
                {"Content-Type": "application/json"},
            )
        from util.audio_detector import anchor_shared_bar_phase

        anchor_shared_bar_phase()
        return json.dumps({"ok": True, "mode": str(mode).strip().lower()}), 200, {
            "Content-Type": "application/json"
        }
    except Exception as e:
        return json.dumps({"error": str(e)}), 500, {"Content-Type": "application/json"}


@controller.post("/stop")
@with_session
async def stop_sequence_playback(request, session):
    """Stop server-driven zone sequence playback."""
    _ = request
    try:
        from util.sequence_playback import stop_playback

        await stop_playback(clear_devices=True)
        return json.dumps({"ok": True}), 200, {"Content-Type": "application/json"}
    except Exception as e:
        return json.dumps({"error": str(e)}), 500, {"Content-Type": "application/json"}


@controller.post("/<id>/play")
@with_session
async def play_sequence(request, session, id):
    """Start server-driven playback for a sequence in a zone (body: {\"zone_id\": \"...\"})."""
    if not get_current_sender():
        return (
            json.dumps({"error": "Transport not configured"}),
            503,
            {"Content-Type": "application/json"},
        )
    current_profile_id = get_current_profile_id(session)
    if not current_profile_id:
        return (
            json.dumps({"error": "No profile available"}),
            404,
            {"Content-Type": "application/json"},
        )
    try:
        data = request.json or {}
    except Exception:
        data = {}
    if not isinstance(data, dict):
        data = {}
    zone_id = data.get("zone_id") or data.get("zoneId")
    if zone_id is None or str(zone_id).strip() == "":
        return (
            json.dumps({"error": "zone_id required"}),
            400,
            {"Content-Type": "application/json"},
        )
    zone_id = str(zone_id).strip()
    try:
        from util.sequence_playback import start

        play_opts = data if isinstance(data, dict) else None
        await start(zone_id, str(id), str(current_profile_id), play_opts)
        from util.sequence_playback import pending_play_status

        body = {"ok": True, **pending_play_status()}
        return json.dumps(body), 200, {"Content-Type": "application/json"}
    except ValueError as e:
        return json.dumps({"error": str(e)}), 400, {"Content-Type": "application/json"}
    except RuntimeError as e:
        return json.dumps({"error": str(e)}), 503, {"Content-Type": "application/json"}
    except Exception as e:
        return json.dumps({"error": str(e)}), 500, {"Content-Type": "application/json"}
