package commerce

import (
	"encoding/json"
	"errors"
	"log/slog"
	"net/http"

	"github.com/go-chi/chi/v5"

	qmiddleware "qycw/go-server/internal/middleware"
	"qycw/go-server/internal/response"
)

type InstanceHandler struct {
	service *Service
	auth    *qmiddleware.Auth
	logger  *slog.Logger
}

func NewInstanceHandler(service *Service, auth *qmiddleware.Auth, logger *slog.Logger) *InstanceHandler {
	return &InstanceHandler{service: service, auth: auth, logger: logger}
}

func (h *InstanceHandler) Routes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireUser)
	r.Get("/", h.listInstances)
	r.Get("/{instance_id}", h.getInstance)
	r.Post("/{instance_id}/sync", h.syncInstance)
	r.Get("/{instance_id}/real-data", h.realData)
	r.Get("/{instance_id}/real-data-history", h.realDataHistory)
	r.Get("/{instance_id}/actions", h.listActions)
	r.Post("/{instance_id}/plugin-actions/{action}", h.pluginAction)
	r.Post("/{instance_id}/vnc", h.vnc)
	r.Post("/{instance_id}/vnc-ticket", h.vnc)
	r.Post("/{instance_id}/reset-password", h.resetPassword)
	r.Put("/{instance_id}/reinstall", h.reinstall)
	r.Post("/{instance_id}/reinstall", h.reinstall)
	r.Get("/{instance_id}/nat-acl", h.listNatACL)
	r.Post("/{instance_id}/nat-acl", h.createNatACL)
	r.Delete("/{instance_id}/nat-acl/{nat_acl_id}", h.deleteNatACL)
	r.Get("/{instance_id}/upgrade/products", h.upgradeProducts)
	r.Post("/{instance_id}/upgrade/quote", h.upgradeQuote)
	r.Post("/{instance_id}/upgrade", h.upgrade)
	r.Post("/{instance_id}/disks/quote", h.diskQuote)
	r.Post("/{instance_id}/disks", h.createDisk)
	r.Post("/{instance_id}/disks/{disk_id}/resize/quote", h.resizeDiskQuote)
	r.Put("/{instance_id}/disks/{disk_id}/resize", h.resizeDisk)
	r.Post("/{instance_id}/disks/{disk_id}/mount", h.mountDisk)
	r.Delete("/{instance_id}/disks/{disk_id}/detach", h.detachDisk)
	r.Delete("/{instance_id}/disks/{disk_id}", h.deleteDisk)
	r.Get("/{instance_id}/snapshots", h.listSnapshots)
	r.Post("/{instance_id}/disks/{disk_id}/snapshots", h.createSnapshot)
	r.Delete("/{instance_id}/snapshots/{snapshot_id}", h.deleteSnapshot)
	r.Post("/{instance_id}/snapshots/{snapshot_id}/restore", h.restoreSnapshot)
	r.Get("/{instance_id}/cron-snapshots", h.cronSnapshots)
	r.Post("/{instance_id}/cron-snapshots", h.createCronSnapshot)
	r.Delete("/{instance_id}/cron-snapshots/{cron_id}", h.deleteCronSnapshot)
	r.Get("/{instance_id}/tasks", h.tasks)
	r.Get("/{instance_id}/monitor", h.monitor)
	r.Get("/{instance_id}/monitor/{chart_type}", h.monitor)
	r.Get("/{instance_id}/finance-firewall", h.financeFirewall)
	r.Post("/{instance_id}/points/claim", h.claimPoints)
	r.Post("/{instance_id}/suspend", h.suspend)
	r.Post("/{instance_id}/unsuspend", h.unsuspend)
	r.Post("/{instance_id}/power-on", h.powerAction("power-on"))
	r.Post("/{instance_id}/shutdown", h.powerAction("shutdown"))
	r.Post("/{instance_id}/hard-shutdown", h.powerAction("hard-shutdown"))
	r.Post("/{instance_id}/reboot", h.powerAction("reboot"))
	r.Patch("/{instance_id}/auto-renew", h.updateAutoRenew)
	r.Post("/{instance_id}/renew/preview", h.previewRenew)
	r.Post("/{instance_id}/renew", h.renew)
	r.Get("/{instance_id}/refund/preview", h.previewRefund)
	r.Post("/{instance_id}/refund", h.refund)
	r.Delete("/{instance_id}", h.refund)
	return r
}

func (h *InstanceHandler) listInstances(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListInstances(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) getInstance(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.GetInstance(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) listActions(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListInstanceActions(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) syncInstance(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.SyncInstanceUpstream(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) realData(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.InstanceRealData(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) realDataHistory(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.InstanceRealDataHistory(r.Context(), userID, instanceID, r.URL.Query())
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) pluginAction(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	req := PluginManageActionRequest{Payload: map[string]any{}}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeInstanceJSON(w, r, &req) {
			return
		}
	}
	action := chi.URLParam(r, "action")
	data, err := h.service.RunPluginManageAction(r.Context(), userID, instanceID, action, req.Payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) vnc(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.FetchInstanceVNC(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) resetPassword(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.ResetInstancePassword(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) reinstall(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.ReinstallInstance(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) listNatACL(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListInstanceNatACL(r.Context(), userID, instanceID, r.URL.Query())
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) createNatACL(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.CreateInstanceNatACL(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) deleteNatACL(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	natACLID, ok := parseID(w, chi.URLParam(r, "nat_acl_id"))
	if !ok {
		return
	}
	data, err := h.service.DeleteInstanceNatACL(r.Context(), userID, instanceID, natACLID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) upgradeProducts(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListInstanceUpgradeProducts(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) upgradeQuote(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.QuoteInstanceUpgrade(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) upgrade(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.UpgradeInstance(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) diskQuote(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.QuoteInstanceDisk(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) createDisk(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.CreateInstanceDisk(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) resizeDiskQuote(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, diskID, ok := h.userInstanceDiskID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.QuoteResizeInstanceDisk(r.Context(), userID, instanceID, diskID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) resizeDisk(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, diskID, ok := h.userInstanceDiskID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.ResizeInstanceDisk(r.Context(), userID, instanceID, diskID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) mountDisk(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, diskID, ok := h.userInstanceDiskID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.MountInstanceDisk(r.Context(), userID, instanceID, diskID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) detachDisk(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, diskID, ok := h.userInstanceDiskID(w, r)
	if !ok {
		return
	}
	data, err := h.service.DetachInstanceDisk(r.Context(), userID, instanceID, diskID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) deleteDisk(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, diskID, ok := h.userInstanceDiskID(w, r)
	if !ok {
		return
	}
	data, err := h.service.DeleteInstanceDisk(r.Context(), userID, instanceID, diskID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) listSnapshots(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListInstanceSnapshots(r.Context(), userID, instanceID, r.URL.Query())
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) createSnapshot(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	diskID, ok := parseID(w, chi.URLParam(r, "disk_id"))
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.CreateInstanceSnapshot(r.Context(), userID, instanceID, diskID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) deleteSnapshot(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	snapshotID, ok := parseID(w, chi.URLParam(r, "snapshot_id"))
	if !ok {
		return
	}
	data, err := h.service.DeleteInstanceSnapshot(r.Context(), userID, instanceID, snapshotID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) restoreSnapshot(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	snapshotID, ok := parseID(w, chi.URLParam(r, "snapshot_id"))
	if !ok {
		return
	}
	data, err := h.service.RestoreInstanceSnapshot(r.Context(), userID, instanceID, snapshotID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) cronSnapshots(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListCronSnapshots(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) createCronSnapshot(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.CreateCronSnapshot(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) deleteCronSnapshot(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	cronID, ok := parseID(w, chi.URLParam(r, "cron_id"))
	if !ok {
		return
	}
	data, err := h.service.DeleteCronSnapshot(r.Context(), userID, instanceID, cronID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) tasks(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListInstanceTasks(r.Context(), userID, instanceID, r.URL.Query())
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) monitor(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	query := r.URL.Query()
	if chartType := chi.URLParam(r, "chart_type"); chartType != "" && query.Get("type") == "" {
		query.Set("type", chartType)
	}
	data, err := h.service.InstanceMonitorChart(r.Context(), userID, instanceID, query)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) financeFirewall(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.GetInstanceFirewall(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) claimPoints(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ClaimInstancePoints(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) suspend(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.SuspendInstance(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) unsuspend(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	payload, ok := h.decodePayload(w, r)
	if !ok {
		return
	}
	data, err := h.service.UnsuspendInstance(r.Context(), userID, instanceID, payload)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) powerAction(action string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID, instanceID, ok := h.userAndInstanceID(w, r)
		if !ok {
			return
		}
		data, err := h.service.RunPowerAction(r.Context(), userID, instanceID, action)
		h.writeResult(w, data, err)
	}
}

func (h *InstanceHandler) decodePayload(w http.ResponseWriter, r *http.Request) (map[string]any, bool) {
	payload := map[string]any{}
	if r.Body == nil || r.ContentLength == 0 {
		return payload, true
	}
	if !decodeInstanceJSON(w, r, &payload) {
		return nil, false
	}
	if nested, ok := payload["payload"].(map[string]any); ok {
		return nested, true
	}
	return payload, true
}

func (h *InstanceHandler) updateAutoRenew(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	var req UpdateAutoRenewRequest
	if !decodeInstanceJSON(w, r, &req) {
		return
	}
	data, err := h.service.UpdateAutoRenew(r.Context(), userID, instanceID, req.AutoRenew)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) previewRenew(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	req := RenewPreviewRequest{Cycles: 1}
	if r.Body != nil && r.ContentLength != 0 {
		if !decodeInstanceJSON(w, r, &req) {
			return
		}
	}
	data, err := h.service.PreviewRenew(r.Context(), userID, instanceID, req.Cycles)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) renew(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	req := RenewInstanceRequest{Cycles: 1}
	if !decodeInstanceJSON(w, r, &req) {
		return
	}
	data, err := h.service.RenewInstance(r.Context(), userID, instanceID, req.Cycles)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) previewRefund(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	data, err := h.service.PreviewInstanceRefund(r.Context(), userID, instanceID)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) refund(w http.ResponseWriter, r *http.Request) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return
	}
	var req RefundInstanceRequest
	if r.Method != http.MethodDelete && r.Body != nil && r.ContentLength != 0 {
		if !decodeInstanceJSON(w, r, &req) {
			return
		}
	}
	data, err := h.service.RefundInstance(r.Context(), userID, instanceID, req)
	h.writeResult(w, data, err)
}

func (h *InstanceHandler) userAndInstanceID(w http.ResponseWriter, r *http.Request) (int64, int64, bool) {
	userID, ok := h.userID(w, r)
	if !ok {
		return 0, 0, false
	}
	instanceID, ok := parseID(w, chi.URLParam(r, "instance_id"))
	if !ok {
		return 0, 0, false
	}
	return userID, instanceID, true
}

func (h *InstanceHandler) userInstanceDiskID(w http.ResponseWriter, r *http.Request) (int64, int64, int64, bool) {
	userID, instanceID, ok := h.userAndInstanceID(w, r)
	if !ok {
		return 0, 0, 0, false
	}
	diskID, ok := parseID(w, chi.URLParam(r, "disk_id"))
	if !ok {
		return 0, 0, 0, false
	}
	return userID, instanceID, diskID, true
}

func (h *InstanceHandler) userID(w http.ResponseWriter, r *http.Request) (int64, bool) {
	principal, ok := qmiddleware.PrincipalFromContext(r.Context())
	if !ok || principal.ID <= 0 {
		response.Detail(w, http.StatusUnauthorized, "Could not validate credentials")
		return 0, false
	}
	return principal.ID, true
}

func (h *InstanceHandler) writeResult(w http.ResponseWriter, data any, err error) {
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, data)
}

func (h *InstanceHandler) writeError(w http.ResponseWriter, err error) {
	var httpErr HTTPError
	if errors.As(err, &httpErr) {
		response.Detail(w, httpErr.Status, httpErr.Detail)
		return
	}
	if h.logger != nil {
		h.logger.Error("instance handler error", "error", err)
	}
	response.Detail(w, http.StatusInternalServerError, msgServiceError)
}

func decodeInstanceJSON(w http.ResponseWriter, r *http.Request, out any) bool {
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(out); err != nil {
		response.Detail(w, http.StatusBadRequest, msgInvalidParams)
		return false
	}
	return true
}
