package api

import (
	"context"
	"errors"
	"fmt"
	"net/http"

	"github.com/go-viper/mapstructure/v2"
)

func (c *Sys) ListMounts() (map[string]*MountOutput, error) {
	return c.ListMountsWithContext(context.Background())
}

func (c *Sys) ListMountsWithContext(ctx context.Context) (map[string]*MountOutput, error) {
	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
	defer cancelFunc()

	r := c.c.NewRequest(http.MethodGet, "/v1/sys/providers")

	r.Params.Set("warden-list", "true")

	resp, err := c.c.rawRequestWithContext(ctx, r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	resource, err := ParseResource(resp.Body)
	if err != nil {
		return nil, err
	}
	if resource == nil || resource.Data == nil {
		return nil, errors.New("data from server response is empty")
	}

	// Extract the mounts field from the response data
	// The Huma framework returns: {"mounts": {...}, "$schema": "..."}
	mountsData, ok := resource.Data["mounts"]
	if !ok {
		return nil, errors.New("mounts field not found in response")
	}

	mounts := map[string]*MountOutput{}
	err = mapstructure.Decode(mountsData, &mounts)
	if err != nil {
		return nil, err
	}

	return mounts, nil
}

func (c *Sys) Mount(path string, mountInfo *MountInput) error {
	return c.MountWithContext(context.Background(), path, mountInfo)
}

func (c *Sys) MountWithContext(ctx context.Context, path string, mountInfo *MountInput) error {
	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
	defer cancelFunc()

	r := c.c.NewRequest(http.MethodPost, fmt.Sprintf("/v1/sys/providers/%s", path))
	if err := r.SetJSONBody(mountInfo); err != nil {
		return err
	}

	resp, err := c.c.rawRequestWithContext(ctx, r)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

func (c *Sys) Unmount(path string) error {
	return c.UnmountWithContext(context.Background(), path)
}

func (c *Sys) UnmountWithContext(ctx context.Context, path string) error {
	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
	defer cancelFunc()

	r := c.c.NewRequest(http.MethodDelete, fmt.Sprintf("/v1/sys/providers/%s", path))

	resp, err := c.c.rawRequestWithContext(ctx, r)
	if resp != nil {
		defer resp.Body.Close()
	}
	return err
}

func (c *Sys) TuneMount(path string, config map[string]any) error {
	return c.TuneMountWithContext(context.Background(), path, config)
}

func (c *Sys) TuneMountWithContext(ctx context.Context, path string, config map[string]any) error {
	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
	defer cancelFunc()

	r := c.c.NewRequest(http.MethodPost, fmt.Sprintf("/v1/sys/providers/%s/tune", path))

	// Wrap config in the expected request body structure
	body := map[string]any{
		"config": config,
	}
	if err := r.SetJSONBody(body); err != nil {
		return err
	}

	resp, err := c.c.rawRequestWithContext(ctx, r)
	if resp != nil {
		defer resp.Body.Close()
	}
	return err
}

func (c *Sys) MountConfig(path string) (map[string]any, error) {
	return c.MountConfigWithContext(context.Background(), path)
}

func (c *Sys) MountConfigWithContext(ctx context.Context, path string) (map[string]any, error) {
	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
	defer cancelFunc()

	r := c.c.NewRequest(http.MethodGet, fmt.Sprintf("/v1/sys/providers/%s/tune", path))

	resp, err := c.c.rawRequestWithContext(ctx, r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	resource, err := ParseResource(resp.Body)
	if err != nil {
		return nil, err
	}
	if resource == nil || resource.Data == nil {
		return nil, errors.New("data from server response is empty")
	}

	var result map[string]any
	err = mapstructure.Decode(resource.Data, &result)
	if err != nil {
		return nil, err
	}

	return result, err
}

func (c *Sys) MountInfo(path string) (*MountOutput, error) {
	return c.MountInfoWithContext(context.Background(), path)
}

func (c *Sys) MountInfoWithContext(ctx context.Context, path string) (*MountOutput, error) {
	ctx, cancelFunc := c.c.withConfiguredTimeout(ctx)
	defer cancelFunc()

	r := c.c.NewRequest(http.MethodGet, fmt.Sprintf("/v1/sys/providers/%s", path))

	resp, err := c.c.rawRequestWithContext(ctx, r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	resource, err := ParseResource(resp.Body)
	if err != nil {
		return nil, err
	}
	if resource == nil || resource.Data == nil {
		return nil, errors.New("data from server response is empty")
	}

	var result MountOutput
	err = mapstructure.Decode(resource.Data, &result)
	if err != nil {
		return nil, err
	}

	return &result, err
}

type MountInput struct {
	Type        string         `json:"type"`
	Description string         `json:"description"`
	Config      map[string]any `json:"config" mapstructure:"config"`
}

type MountOutput struct {
	Type        string         `json:"type"`
	Description string         `json:"description"`
	Accessor    string         `json:"accessor"`
	Config      map[string]any `json:"config" mapstructure:"config"`
	// MountURL is the relative URL path that resolves to this mount.
	// Agents append it to $WARDEN_ADDR plus the per-provider suffix
	// (e.g. "gateway", "role/<role>/gateway", "access/<grant>") to reach
	// the upstream. Has the namespace and mount path already baked in.
	MountURL string `json:"mount_url" mapstructure:"mount_url"`
}
