using GLib;
using Gee;

public class Repository : DBusServiceProxy {
    public signal void conversations_updated();
    public signal void messages_updated(string conversation_guid);
    public signal void attachment_downloaded(string attachment_guid);
    public signal void attachment_uploaded(string upload_guid, string attachment_guid);
    public signal void reconnected();

    public static Repository get_instance() {
        if (instance == null) {
            instance = new Repository();
        }

        return instance;
    }

    private static Repository instance = null;
    private DBusService.Repository? dbus_repository;
    private uint dbus_watch_id;

    private Repository() {
        this.dbus_watch_id = Bus.watch_name(BusType.SESSION, DBUS_NAME, BusNameWatcherFlags.AUTO_START, (name, name_owner) => {
            connect_to_repository();
        });
    }

    private void connect_to_repository() {
        GLib.info("Connecting to repository");

        try {
            this.dbus_repository = Bus.get_proxy_sync<DBusService.Repository>(BusType.SESSION, DBUS_NAME, DBUS_PATH);
            this.dbus_repository.conversations_updated.connect(() => {
                conversations_updated();
            });
            
            this.dbus_repository.messages_updated.connect((conversation_guid) => {
                messages_updated(conversation_guid);
            });

            this.dbus_repository.attachment_download_completed.connect((attachment_guid) => {
                attachment_downloaded(attachment_guid);
            });

            this.dbus_repository.attachment_upload_completed.connect((upload_guid, attachment_guid) => {
                attachment_uploaded(upload_guid, attachment_guid);
            });

            this.dbus_repository.update_stream_reconnected.connect(() => {
                reconnected();
            });

            conversations_updated();
            reconnected();
        } catch (GLib.Error e) {
            warning("Failed to connect to repository: %s", e.message);
        }
    }

    public void sync_conversation_list() throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }

        dbus_repository.sync_conversation_list();
    }
        
    public Conversation[] get_conversations(int limit = 200) throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }

        var conversations = dbus_repository.get_conversations(limit, 0);
        Conversation[] returned_conversations = new Conversation[conversations.length];

        for (int i = 0; i < conversations.length; i++) {
            returned_conversations[i] = new Conversation.from_hash_table(conversations[i]);
        }
        
        return returned_conversations;
    }

    public Message[] get_messages(string conversation_guid, string last_message_id = "") throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }
        
        var messages = dbus_repository.get_messages(conversation_guid, last_message_id);
        Message[] returned_messages = new Message[messages.length];

        for (int i = 0; i < messages.length; i++) {
            returned_messages[i] = new Message.from_hash_table(messages[i]);
        }

        return returned_messages;
    }

    public string send_message(string conversation_guid, string message, string[] attachment_guids) throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }

        return dbus_repository.send_message(conversation_guid, message, attachment_guids);
    }

    public void sync_conversation(string conversation_guid) throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }

        dbus_repository.sync_conversation(conversation_guid);
    }

    public void mark_conversation_as_read(string conversation_guid) throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }

        dbus_repository.mark_conversation_as_read(conversation_guid);
    }

    public void download_attachment(string attachment_guid, bool preview) throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }

        dbus_repository.download_attachment(attachment_guid, preview);
    }

    public string upload_attachment(string filename) throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }

        return dbus_repository.upload_attachment(filename);
    }

    public AttachmentInfo get_attachment_info(string attachment_guid) throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }

        var info = dbus_repository.get_attachment_info(attachment_guid);
        return new AttachmentInfo(info.attr1, info.attr2, info.attr3, info.attr4);
    }

    public int open_attachment_fd(string attachment_guid, bool preview) throws DBusServiceProxyError, GLib.Error {
        if (dbus_repository == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Repository not connected");
        }

        var connection = Bus.get_sync(BusType.SESSION);
        UnixFDList? out_fd_list = null;
        var result = connection.call_with_unix_fd_list_sync(
            DBUS_NAME,
            DBUS_PATH,
            "net.buzzert.kordophone.Repository",
            "OpenAttachmentFd",
            new Variant("(sb)", attachment_guid, preview),
            new VariantType("(h)"),
            DBusCallFlags.NONE,
            120000,
            null,
            out out_fd_list,
            null
        );

        int fd_handle = -1;
        result.get("(h)", out fd_handle);

        if (out_fd_list == null) {
            throw new DBusServiceProxyError.NOT_CONNECTED("Missing UnixFDList from OpenAttachmentFd");
        }

        return out_fd_list.get(fd_handle);
    }
}
