package docker

import (
	"encoding/json"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/rclone/rclone/fs"
)

const (
	contentType  = "application/vnd.docker.plugins.v1.1+json"
	activatePath = "/Plugin.Activate"
	createPath   = "/VolumeDriver.Create"
	getPath      = "/VolumeDriver.Get"
	listPath     = "/VolumeDriver.List"
	removePath   = "/VolumeDriver.Remove"
	pathPath     = "/VolumeDriver.Path"
	mountPath    = "/VolumeDriver.Mount"
	unmountPath  = "/VolumeDriver.Unmount"
	capsPath     = "/VolumeDriver.Capabilities"
)

// CreateRequest is the structure that docker's requests are deserialized to.
type CreateRequest struct {
	Name    string
	Options map[string]string `json:"Opts,omitempty"`
}

// RemoveRequest structure for a volume remove request
type RemoveRequest struct {
	Name string
}

// MountRequest structure for a volume mount request
type MountRequest struct {
	Name string
	ID   string
}

// MountResponse structure for a volume mount response
type MountResponse struct {
	Mountpoint string
}

// UnmountRequest structure for a volume unmount request
type UnmountRequest struct {
	Name string
	ID   string
}

// PathRequest structure for a volume path request
type PathRequest struct {
	Name string
}

// PathResponse structure for a volume path response
type PathResponse struct {
	Mountpoint string
}

// GetRequest structure for a volume get request
type GetRequest struct {
	Name string
}

// GetResponse structure for a volume get response
type GetResponse struct {
	Volume *VolInfo
}

// ListResponse structure for a volume list response
type ListResponse struct {
	Volumes []*VolInfo
}

// CapabilitiesResponse structure for a volume capability response
type CapabilitiesResponse struct {
	Capabilities Capability
}

// Capability represents the list of capabilities a volume driver can return
type Capability struct {
	Scope string
}

// ErrorResponse is a formatted error message that docker can understand
type ErrorResponse struct {
	Err string
}

func newRouter(drv *Driver) http.Handler {
	r := chi.NewRouter()
	r.Post(activatePath, func(w http.ResponseWriter, r *http.Request) {
		res := map[string]any{
			"Implements": []string{"VolumeDriver"},
		}
		encodeResponse(w, res, nil, activatePath)
	})
	r.Post(createPath, func(w http.ResponseWriter, r *http.Request) {
		var req CreateRequest
		if decodeRequest(w, r, &req) {
			err := drv.Create(&req)
			encodeResponse(w, nil, err, createPath)
		}
	})
	r.Post(removePath, func(w http.ResponseWriter, r *http.Request) {
		var req RemoveRequest
		if decodeRequest(w, r, &req) {
			err := drv.Remove(&req)
			encodeResponse(w, nil, err, removePath)
		}
	})
	r.Post(mountPath, func(w http.ResponseWriter, r *http.Request) {
		var req MountRequest
		if decodeRequest(w, r, &req) {
			res, err := drv.Mount(&req)
			encodeResponse(w, res, err, mountPath)
		}
	})
	r.Post(pathPath, func(w http.ResponseWriter, r *http.Request) {
		var req PathRequest
		if decodeRequest(w, r, &req) {
			res, err := drv.Path(&req)
			encodeResponse(w, res, err, pathPath)
		}
	})
	r.Post(getPath, func(w http.ResponseWriter, r *http.Request) {
		var req GetRequest
		if decodeRequest(w, r, &req) {
			res, err := drv.Get(&req)
			encodeResponse(w, res, err, getPath)
		}
	})
	r.Post(unmountPath, func(w http.ResponseWriter, r *http.Request) {
		var req UnmountRequest
		if decodeRequest(w, r, &req) {
			err := drv.Unmount(&req)
			encodeResponse(w, nil, err, unmountPath)
		}
	})
	r.Post(listPath, func(w http.ResponseWriter, r *http.Request) {
		res, err := drv.List()
		encodeResponse(w, res, err, listPath)
	})
	r.Post(capsPath, func(w http.ResponseWriter, r *http.Request) {
		res := &CapabilitiesResponse{
			Capabilities: Capability{Scope: pluginScope},
		}
		encodeResponse(w, res, nil, capsPath)
	})
	return r
}

func decodeRequest(w http.ResponseWriter, r *http.Request, req any) bool {
	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return false
	}
	return true
}

func encodeResponse(w http.ResponseWriter, res any, err error, path string) {
	w.Header().Set("Content-Type", contentType)
	if err != nil {
		fs.Debugf(path, "Request returned error: %v", err)
		w.WriteHeader(http.StatusInternalServerError)
		res = &ErrorResponse{Err: err.Error()}
	} else if res == nil {
		res = struct{}{}
	}
	if err = json.NewEncoder(w).Encode(res); err != nil {
		fs.Debugf(path, "Response encoding failed: %v", err)
	}
}
