#include "mpris.h"
#include <gio/gio.h>
#include <string.h>
#include <stdio.h>

/* MPRIS D-Bus introspection XML */
static const gchar introspection_xml[] =
  "<node>"
  "  <interface name='org.mpris.MediaPlayer2'>"
  "    <method name='Raise'/>"
  "    <method name='Quit'/>"
  "    <method name='AddFiles'>"
  "      <arg type='as' name='Files' direction='in'/>"
  "    </method>"
  "    <property name='CanQuit' type='b' access='read'/>"
  "    <property name='CanRaise' type='b' access='read'/>"
  "    <property name='HasTrackList' type='b' access='read'/>"
  "    <property name='Identity' type='s' access='read'/>"
  "    <property name='SupportedUriSchemes' type='as' access='read'/>"
  "    <property name='SupportedMimeTypes' type='as' access='read'/>"
  "  </interface>"
  "  <interface name='org.mpris.MediaPlayer2.Player'>"
  "    <method name='Next'/>"
  "    <method name='Previous'/>"
  "    <method name='Pause'/>"
  "    <method name='PlayPause'/>"
  "    <method name='Stop'/>"
  "    <method name='Play'/>"
  "    <method name='Seek'>"
  "      <arg type='x' name='Offset' direction='in'/>"
  "    </method>"
  "    <method name='SetPosition'>"
  "      <arg type='o' name='TrackId' direction='in'/>"
  "      <arg type='x' name='Position' direction='in'/>"
  "    </method>"
  "    <method name='OpenUri'>"
  "      <arg type='s' name='Uri' direction='in'/>"
  "    </method>"
  "    <property name='PlaybackStatus' type='s' access='read'/>"
  "    <property name='LoopStatus' type='s' access='readwrite'/>"
  "    <property name='Rate' type='d' access='readwrite'/>"
  "    <property name='Shuffle' type='b' access='readwrite'/>"
  "    <property name='Metadata' type='a{sv}' access='read'/>"
  "    <property name='Volume' type='d' access='readwrite'/>"
  "    <property name='Position' type='x' access='read'/>"
  "    <property name='MinimumRate' type='d' access='read'/>"
  "    <property name='MaximumRate' type='d' access='read'/>"
  "    <property name='CanGoNext' type='b' access='read'/>"
  "    <property name='CanGoPrevious' type='b' access='read'/>"
  "    <property name='CanPlay' type='b' access='read'/>"
  "    <property name='CanPause' type='b' access='read'/>"
  "    <property name='CanSeek' type='b' access='read'/>"
  "    <property name='CanControl' type='b' access='read'/>"
  "    <signal name='Seeked'>"
  "      <arg type='x' name='Position'/>"
  "    </signal>"
  "  </interface>"
  "</node>";

typedef struct {
    AppUI *ui;
    GDBusConnection *connection;
    guint registration_id;
    guint owner_id;
} MPRISData;

static MPRISData *mpris_data = NULL;

/* Method Call Handlers */
static void handle_method_call(GDBusConnection *connection,
                              const gchar *sender,
                              const gchar *object_path,
                              const gchar *interface_name,
                              const gchar *method_name,
                              GVariant *parameters,
                              GDBusMethodInvocation *invocation,
                              gpointer user_data) {
    MPRISData *data = (MPRISData *)user_data;
    Player *player = ui_get_player(data->ui);
    (void)connection; (void)sender; (void)object_path; (void)parameters;

    if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2") == 0) {
        if (g_strcmp0(method_name, "Raise") == 0) {
            gtk_window_present(GTK_WINDOW(ui_get_window(data->ui)));
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "Quit") == 0) {
            gtk_main_quit();
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "AddFiles") == 0) {
            GVariantIter *iter;
            g_variant_get(parameters, "(as)", &iter);
            gchar *file;
            Playlist *playlist = ui_get_playlist(data->ui);
            while (g_variant_iter_next(iter, "s", &file)) {
                playlist_add_file(playlist, file, TRUE);
                g_free(file);
            }
            g_variant_iter_free(iter);
            g_dbus_method_invocation_return_value(invocation, NULL);
        }
    } else if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2.Player") == 0) {
        if (g_strcmp0(method_name, "Next") == 0) {
            player_playlist_next(player);
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "Previous") == 0) {
            player_playlist_prev(player);
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "Pause") == 0) {
            player_pause(player);
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "PlayPause") == 0) {
            player_toggle_pause(player);
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "Stop") == 0) {
            player_stop(player);
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "Play") == 0) {
            player_play(player);
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "Seek") == 0) {
            gint64 offset;
            g_variant_get(parameters, "(x)", &offset);
            player_seek(player, (double)offset / 1000000.0);
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "SetPosition") == 0) {
            const gchar *track_id;
            gint64 position;
            g_variant_get(parameters, "(&ox)", &track_id, &position);
            player_seek_absolute(player, (double)position / 1000000.0);
            g_dbus_method_invocation_return_value(invocation, NULL);
        } else if (g_strcmp0(method_name, "OpenUri") == 0) {
            const gchar *uri;
            g_variant_get(parameters, "(&s)", &uri);
            Playlist *playlist = ui_get_playlist(data->ui);
            playlist_clear(playlist);
            playlist_add_file(playlist, uri, FALSE);
            g_dbus_method_invocation_return_value(invocation, NULL);
        }
    }
}

/* Property Get Handlers */
static GVariant *handle_get_property(GDBusConnection *connection,
                                    const gchar *sender,
                                    const gchar *object_path,
                                    const gchar *interface_name,
                                    const gchar *property_name,
                                    GError **error,
                                    gpointer user_data) {
    MPRISData *data = (MPRISData *)user_data;
    Player *player = ui_get_player(data->ui);
    (void)connection; (void)sender; (void)object_path; (void)error;

    if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2") == 0) {
        if (g_strcmp0(property_name, "CanQuit") == 0) return g_variant_new_boolean(TRUE);
        if (g_strcmp0(property_name, "CanRaise") == 0) return g_variant_new_boolean(TRUE);
        if (g_strcmp0(property_name, "HasTrackList") == 0) return g_variant_new_boolean(FALSE);
        if (g_strcmp0(property_name, "Identity") == 0) return g_variant_new_string("Kino");
        if (g_strcmp0(property_name, "SupportedUriSchemes") == 0) {
            const gchar *schemes[] = {"file", "http", "https", "rtsp", "rtmp", "mms", NULL};
            return g_variant_new_strv(schemes, -1);
        }
        if (g_strcmp0(property_name, "SupportedMimeTypes") == 0) {
            const gchar *mimes[] = {"video/mp4", "video/x-matroska", "video/webm", "video/x-msvideo", "audio/mpeg", "audio/ogg", "audio/x-wav", "audio/flac", NULL};
            return g_variant_new_strv(mimes, -1);
        }
    } else if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2.Player") == 0) {
        if (g_strcmp0(property_name, "PlaybackStatus") == 0) {
            if (player_is_idle(player)) return g_variant_new_string("Stopped");
            return g_variant_new_string(player_get_paused(player) ? "Paused" : "Playing");
        }
        if (g_strcmp0(property_name, "LoopStatus") == 0) {
            Playlist *playlist = ui_get_playlist(data->ui);
            PlaylistRepeatMode mode = playlist_get_repeat_mode(playlist);
            if (mode == PLAYLIST_REPEAT_NONE) return g_variant_new_string("None");
            if (mode == PLAYLIST_REPEAT_ONE) return g_variant_new_string("Track");
            return g_variant_new_string("Playlist");
        }
        if (g_strcmp0(property_name, "Rate") == 0) return g_variant_new_double(player_get_speed(player));
        if (g_strcmp0(property_name, "Shuffle") == 0) {
            Playlist *playlist = ui_get_playlist(data->ui);
            return g_variant_new_boolean(playlist_get_shuffle(playlist));
        }
        if (g_strcmp0(property_name, "Volume") == 0) return g_variant_new_double(player_get_volume(player) / 100.0);
        if (g_strcmp0(property_name, "Position") == 0) return g_variant_new_int64((gint64)(player_get_position(player) * 1000000.0));
        if (g_strcmp0(property_name, "MinimumRate") == 0) return g_variant_new_double(0.1);
        if (g_strcmp0(property_name, "MaximumRate") == 0) return g_variant_new_double(10.0);
        if (g_strcmp0(property_name, "CanGoNext") == 0) return g_variant_new_boolean(TRUE);
        if (g_strcmp0(property_name, "CanGoPrevious") == 0) return g_variant_new_boolean(TRUE);
        if (g_strcmp0(property_name, "CanPlay") == 0) return g_variant_new_boolean(TRUE);
        if (g_strcmp0(property_name, "CanPause") == 0) return g_variant_new_boolean(TRUE);
        if (g_strcmp0(property_name, "CanSeek") == 0) return g_variant_new_boolean(TRUE);
        if (g_strcmp0(property_name, "CanControl") == 0) return g_variant_new_boolean(TRUE);
        if (g_strcmp0(property_name, "Metadata") == 0) {
            GVariantBuilder builder;
            g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
            
            char *title = player_get_media_title(player);
            if (title) {
                g_variant_builder_add(&builder, "{sv}", "xesam:title", g_variant_new_variant(g_variant_new_string(title)));
                g_free(title);
            }
            
            char *artist = player_get_metadata_tag(player, "artist");
            if (artist) {
                const gchar *artists[] = {artist, NULL};
                g_variant_builder_add(&builder, "{sv}", "xesam:artist", g_variant_new_variant(g_variant_new_strv(artists, -1)));
                g_free(artist);
            }

            char *album_artist = player_get_metadata_tag(player, "album_artist");
            if (album_artist) {
                const gchar *album_artists[] = {album_artist, NULL};
                g_variant_builder_add(&builder, "{sv}", "xesam:albumArtist", g_variant_new_variant(g_variant_new_strv(album_artists, -1)));
                g_free(album_artist);
            }

            char *album = player_get_metadata_tag(player, "album");
            if (album) {
                g_variant_builder_add(&builder, "{sv}", "xesam:album", g_variant_new_variant(g_variant_new_string(album)));
                g_free(album);
            }

            char *genre = player_get_metadata_tag(player, "genre");
            if (genre) {
                const gchar *genres[] = {genre, NULL};
                g_variant_builder_add(&builder, "{sv}", "xesam:genre", g_variant_new_variant(g_variant_new_strv(genres, -1)));
                g_free(genre);
            }

            char *date = player_get_metadata_tag(player, "date");
            if (date) {
                g_variant_builder_add(&builder, "{sv}", "xesam:contentCreated", g_variant_new_variant(g_variant_new_string(date)));
                g_free(date);
            }

            char *composer = player_get_metadata_tag(player, "composer");
            if (composer) {
                const gchar *composers[] = {composer, NULL};
                g_variant_builder_add(&builder, "{sv}", "xesam:composer", g_variant_new_variant(g_variant_new_strv(composers, -1)));
                g_free(composer);
            }

            char *comment = player_get_metadata_tag(player, "comment");
            if (comment) {
                const gchar *comments[] = {comment, NULL};
                g_variant_builder_add(&builder, "{sv}", "xesam:comment", g_variant_new_variant(g_variant_new_strv(comments, -1)));
                g_free(comment);
            }

            char *track = player_get_metadata_tag(player, "track");
            if (track) {
                int track_num = atoi(track);
                if (track_num > 0)
                    g_variant_builder_add(&builder, "{sv}", "xesam:trackNumber", g_variant_new_variant(g_variant_new_int32(track_num)));
                g_free(track);
            }

            char *disc = player_get_metadata_tag(player, "disc");
            if (disc) {
                int disc_num = atoi(disc);
                if (disc_num > 0)
                    g_variant_builder_add(&builder, "{sv}", "xesam:discNumber", g_variant_new_variant(g_variant_new_int32(disc_num)));
                g_free(disc);
            }

            char *url = player_get_filename(player);
            if (url) {
                g_variant_builder_add(&builder, "{sv}", "xesam:url", g_variant_new_variant(g_variant_new_string(url)));
                g_free(url);
            }
            
            double duration = player_get_duration(player);
            if (duration > 0) {
                g_variant_builder_add(&builder, "{sv}", "mpris:length", g_variant_new_variant(g_variant_new_int64((gint64)(duration * 1000000.0))));
            }
            
            g_variant_builder_add(&builder, "{sv}", "mpris:trackid", g_variant_new_variant(g_variant_new_object_path("/org/mpris/MediaPlayer2/Track/0")));
            
            return g_variant_builder_end(&builder);
        }
    }
    return NULL;
}

/* Property Set Handlers */
static gboolean handle_set_property(GDBusConnection *connection,
                                    const gchar *sender,
                                    const gchar *object_path,
                                    const gchar *interface_name,
                                    const gchar *property_name,
                                    GVariant *value,
                                    GError **error,
                                    gpointer user_data) {
    MPRISData *data = (MPRISData *)user_data;
    Player *player = ui_get_player(data->ui);
    (void)connection; (void)sender; (void)object_path; (void)interface_name; (void)error;

    if (g_strcmp0(property_name, "Volume") == 0) {
        player_set_volume(player, g_variant_get_double(value) * 100.0);
        return TRUE;
    }
    if (g_strcmp0(property_name, "Rate") == 0) {
        player_set_speed(player, g_variant_get_double(value));
        return TRUE;
    }
    if (g_strcmp0(property_name, "Shuffle") == 0) {
        playlist_set_shuffle(ui_get_playlist(data->ui), g_variant_get_boolean(value));
        return TRUE;
    }
    if (g_strcmp0(property_name, "LoopStatus") == 0) {
        const gchar *status = g_variant_get_string(value, NULL);
        Playlist *playlist = ui_get_playlist(data->ui);
        if (g_strcmp0(status, "None") == 0) playlist_set_repeat_mode(playlist, PLAYLIST_REPEAT_NONE);
        else if (g_strcmp0(status, "Track") == 0) playlist_set_repeat_mode(playlist, PLAYLIST_REPEAT_ONE);
        else if (g_strcmp0(status, "Playlist") == 0) playlist_set_repeat_mode(playlist, PLAYLIST_REPEAT_ALL);
        return TRUE;
    }
    return FALSE;
}

static const GDBusInterfaceVTable interface_vtable = {
    .method_call = handle_method_call,
    .get_property = handle_get_property,
    .set_property = handle_set_property
};

static void on_bus_acquired(GDBusConnection *connection, const gchar *name, gpointer user_data) {
    MPRISData *data = (MPRISData *)user_data;
    data->connection = connection;
    (void)name;

    GDBusNodeInfo *introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL);
    if (!introspection_data) return;

    data->registration_id = g_dbus_connection_register_object(connection,
        "/org/mpris/MediaPlayer2",
        introspection_data->interfaces[0],
        &interface_vtable,
        data, NULL, NULL);
    
    g_dbus_connection_register_object(connection,
        "/org/mpris/MediaPlayer2",
        introspection_data->interfaces[1],
        &interface_vtable,
        data, NULL, NULL);

    g_dbus_node_info_unref(introspection_data);
}

void mpris_init(AppUI *ui) {
    if (mpris_data) return;
    
    mpris_data = g_new0(MPRISData, 1);
    mpris_data->ui = ui;
    
    mpris_data->owner_id = g_bus_own_name(G_BUS_TYPE_SESSION,
        "org.mpris.MediaPlayer2.gtk2_mpv_player",
        G_BUS_NAME_OWNER_FLAGS_NONE,
        on_bus_acquired,
        NULL, NULL,
        mpris_data, NULL);
    
    /* Initial state update */
    mpris_update_playback_status(ui);
}

void mpris_destroy(AppUI *ui) {
    (void)ui;
    if (!mpris_data) return;
    
    if (mpris_data->owner_id > 0) g_bus_unown_name(mpris_data->owner_id);
    g_free(mpris_data);
    mpris_data = NULL;
}

/* Update Helpers */
static void mpris_emit_properties_changed(const char *interface, GVariant *changed_props) {
    if (!mpris_data || !mpris_data->connection || !changed_props) {
        if (changed_props) g_variant_unref(g_variant_ref_sink(changed_props));
        return;
    }
    
    const gchar *invalidated[] = { NULL };
    g_dbus_connection_emit_signal(mpris_data->connection,
        NULL,
        "/org/mpris/MediaPlayer2",
        "org.freedesktop.DBus.Properties",
        "PropertiesChanged",
        g_variant_new("(s@a{sv}^as)",
            interface,
            changed_props,
            invalidated),
        NULL);
}

static void append_metadata(Player *player, GVariantBuilder *builder) {
    GVariantBuilder meta_builder;
    g_variant_builder_init(&meta_builder, G_VARIANT_TYPE("a{sv}"));

    char *title = player_get_media_title(player);
    if (!title) title = player_get_filename(player);
    
    if (title) {
        g_variant_builder_add(&meta_builder, "{sv}", "xesam:title", g_variant_new_variant(g_variant_new_string(title)));
        g_free(title);
    }
    
    double duration = player_get_duration(player);
    if (duration > 0) {
        g_variant_builder_add(&meta_builder, "{sv}", "mpris:length", g_variant_new_variant(g_variant_new_int64((gint64)(duration * 1000000.0))));
    }
    
    g_variant_builder_add(&meta_builder, "{sv}", "mpris:trackid", g_variant_new_variant(g_variant_new_object_path("/org/mpris/MediaPlayer2/Track/0")));
    
    g_variant_builder_add(builder, "{sv}", "Metadata", g_variant_new_variant(g_variant_builder_end(&meta_builder)));
}

void mpris_update_playback_status(AppUI *ui) {
    if (!mpris_data || !mpris_data->connection) return;
    Player *player = ui_get_player(ui);
    
    gboolean is_idle = player_is_idle(player);
    gboolean is_paused = player_get_paused(player);
    const char *status = is_idle ? "Stopped" : (is_paused ? "Paused" : "Playing");
    
    GVariantBuilder builder;
    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
    g_variant_builder_add(&builder, "{sv}", "PlaybackStatus", g_variant_new_variant(g_variant_new_string(status)));
    g_variant_builder_add(&builder, "{sv}", "Position", g_variant_new_variant(g_variant_new_int64((gint64)(player_get_position(player) * 1000000.0))));
    
    /* Include metadata in the same signal for efficiency and consistency */
    append_metadata(player, &builder);
    
    mpris_emit_properties_changed("org.mpris.MediaPlayer2.Player", g_variant_builder_end(&builder));
}

void mpris_update_metadata(AppUI *ui) {
    if (!mpris_data || !mpris_data->connection) return;
    Player *player = ui_get_player(ui);
    
    GVariantBuilder builder;
    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
    append_metadata(player, &builder);
    
    mpris_emit_properties_changed("org.mpris.MediaPlayer2.Player", g_variant_builder_end(&builder));
}

void mpris_update_volume(AppUI *ui) {
    if (!mpris_data || !mpris_data->connection) return;
    Player *player = ui_get_player(ui);
    
    GVariantBuilder builder;
    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
    g_variant_builder_add(&builder, "{sv}", "Volume", g_variant_new_variant(g_variant_new_double(player_get_volume(player) / 100.0)));
    
    mpris_emit_properties_changed("org.mpris.MediaPlayer2.Player", g_variant_builder_end(&builder));
}

void mpris_update_position(AppUI *ui) {
    if (!mpris_data || !mpris_data->connection) return;
    Player *player = ui_get_player(ui);
    
    GVariantBuilder builder;
    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
    g_variant_builder_add(&builder, "{sv}", "Position", g_variant_new_variant(g_variant_new_int64((gint64)(player_get_position(player) * 1000000.0))));
    
    mpris_emit_properties_changed("org.mpris.MediaPlayer2.Player", g_variant_builder_end(&builder));
}

void mpris_update_loop_shuffle(AppUI *ui) {
    if (!mpris_data || !mpris_data->connection) return;
    Playlist *playlist = ui_get_playlist(ui);
    
    GVariantBuilder builder;
    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
    
    PlaylistRepeatMode mode = playlist_get_repeat_mode(playlist);
    const char *loop_status = "None";
    if (mode == PLAYLIST_REPEAT_ONE) loop_status = "Track";
    else if (mode == PLAYLIST_REPEAT_ALL) loop_status = "Playlist";
    
    g_variant_builder_add(&builder, "{sv}", "LoopStatus", g_variant_new_variant(g_variant_new_string(loop_status)));
    g_variant_builder_add(&builder, "{sv}", "Shuffle", g_variant_new_variant(g_variant_new_boolean(playlist_get_shuffle(playlist))));
    
    mpris_emit_properties_changed("org.mpris.MediaPlayer2.Player", g_variant_builder_end(&builder));
}

void mpris_emit_seeked(AppUI *ui, gint64 position_us) {
    if (!mpris_data || !mpris_data->connection) return;
    (void)ui;
    
    g_dbus_connection_emit_signal(mpris_data->connection,
        NULL,
        "/org/mpris/MediaPlayer2",
        "org.mpris.MediaPlayer2.Player",
        "Seeked",
        g_variant_new("(x)", position_us),
        NULL);
}
