package dbus

import (
	"fmt"
	"net"

	"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
	"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
)

type objectParams struct {
	bus   string
	dest  string
	path  string
	iface string
}

func extractObjectParams(p map[string]any, requirePath bool) (objectParams, error) {
	bus, err := params.String(p, "bus")
	if err != nil {
		return objectParams{}, err
	}
	dest, err := params.String(p, "dest")
	if err != nil {
		return objectParams{}, err
	}

	var path string
	if requirePath {
		path, err = params.String(p, "path")
		if err != nil {
			return objectParams{}, err
		}
	} else {
		path = params.StringOpt(p, "path", "/")
	}

	iface, err := params.String(p, "interface")
	if err != nil {
		return objectParams{}, err
	}

	return objectParams{bus: bus, dest: dest, path: path, iface: iface}, nil
}

func HandleRequest(conn net.Conn, req models.Request, m *Manager, clientID string) {
	switch req.Method {
	case "dbus.call":
		handleCall(conn, req, m)
	case "dbus.getProperty":
		handleGetProperty(conn, req, m)
	case "dbus.setProperty":
		handleSetProperty(conn, req, m)
	case "dbus.getAllProperties":
		handleGetAllProperties(conn, req, m)
	case "dbus.introspect":
		handleIntrospect(conn, req, m)
	case "dbus.listNames":
		handleListNames(conn, req, m)
	case "dbus.subscribe":
		handleSubscribe(conn, req, m, clientID)
	case "dbus.unsubscribe":
		handleUnsubscribe(conn, req, m)
	default:
		models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
	}
}

func handleCall(conn net.Conn, req models.Request, m *Manager) {
	op, err := extractObjectParams(req.Params, true)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	method, err := params.String(req.Params, "method")
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	var args []any
	if argsRaw, ok := params.Any(req.Params, "args"); ok {
		if argsSlice, ok := argsRaw.([]any); ok {
			args = argsSlice
		}
	}

	result, err := m.Call(op.bus, op.dest, op.path, op.iface, method, args)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	models.Respond(conn, req.ID, result)
}

func handleGetProperty(conn net.Conn, req models.Request, m *Manager) {
	op, err := extractObjectParams(req.Params, true)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	property, err := params.String(req.Params, "property")
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	result, err := m.GetProperty(op.bus, op.dest, op.path, op.iface, property)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	models.Respond(conn, req.ID, result)
}

func handleSetProperty(conn net.Conn, req models.Request, m *Manager) {
	op, err := extractObjectParams(req.Params, true)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	property, err := params.String(req.Params, "property")
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	value, ok := params.Any(req.Params, "value")
	if !ok {
		models.RespondError(conn, req.ID, "missing 'value' parameter")
		return
	}

	if err := m.SetProperty(op.bus, op.dest, op.path, op.iface, property, value); err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	models.Respond(conn, req.ID, models.SuccessResult{Success: true})
}

func handleGetAllProperties(conn net.Conn, req models.Request, m *Manager) {
	op, err := extractObjectParams(req.Params, true)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	result, err := m.GetAllProperties(op.bus, op.dest, op.path, op.iface)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	models.Respond(conn, req.ID, result)
}

func handleIntrospect(conn net.Conn, req models.Request, m *Manager) {
	bus, err := params.String(req.Params, "bus")
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	dest, err := params.String(req.Params, "dest")
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	path := params.StringOpt(req.Params, "path", "/")

	result, err := m.Introspect(bus, dest, path)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	models.Respond(conn, req.ID, result)
}

func handleListNames(conn net.Conn, req models.Request, m *Manager) {
	bus, err := params.String(req.Params, "bus")
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	result, err := m.ListNames(bus)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	models.Respond(conn, req.ID, result)
}

func handleSubscribe(conn net.Conn, req models.Request, m *Manager, clientID string) {
	bus, err := params.String(req.Params, "bus")
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	sender := params.StringOpt(req.Params, "sender", "")
	path := params.StringOpt(req.Params, "path", "")
	iface := params.StringOpt(req.Params, "interface", "")
	member := params.StringOpt(req.Params, "member", "")

	result, err := m.Subscribe(clientID, bus, sender, path, iface, member)
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	models.Respond(conn, req.ID, result)
}

func handleUnsubscribe(conn net.Conn, req models.Request, m *Manager) {
	subID, err := params.String(req.Params, "subscriptionId")
	if err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	if err := m.Unsubscribe(subID); err != nil {
		models.RespondError(conn, req.ID, err.Error())
		return
	}

	models.Respond(conn, req.ID, models.SuccessResult{Success: true})
}
