package v3

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"

	"github.com/grafana/grafana-app-sdk/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type TestKindClient struct {
	client *resource.TypedClient[*TestKind, *TestKindList]
}

func NewTestKindClient(client resource.Client) *TestKindClient {
	return &TestKindClient{
		client: resource.NewTypedClient[*TestKind, *TestKindList](client, TestKindKind()),
	}
}

func NewTestKindClientFromGenerator(generator resource.ClientGenerator) (*TestKindClient, error) {
	c, err := generator.ClientFor(TestKindKind())
	if err != nil {
		return nil, err
	}
	return NewTestKindClient(c), nil
}

func (c *TestKindClient) Get(ctx context.Context, identifier resource.Identifier) (*TestKind, error) {
	return c.client.Get(ctx, identifier)
}

func (c *TestKindClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*TestKindList, error) {
	return c.client.List(ctx, namespace, opts)
}

func (c *TestKindClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*TestKindList, error) {
	resp, err := c.client.List(ctx, namespace, resource.ListOptions{
		ResourceVersion: opts.ResourceVersion,
		Limit:           opts.Limit,
		LabelFilters:    opts.LabelFilters,
		FieldSelectors:  opts.FieldSelectors,
	})
	if err != nil {
		return nil, err
	}
	for resp.GetContinue() != "" {
		page, err := c.client.List(ctx, namespace, resource.ListOptions{
			Continue:        resp.GetContinue(),
			ResourceVersion: opts.ResourceVersion,
			Limit:           opts.Limit,
			LabelFilters:    opts.LabelFilters,
			FieldSelectors:  opts.FieldSelectors,
		})
		if err != nil {
			return nil, err
		}
		resp.SetContinue(page.GetContinue())
		resp.SetResourceVersion(page.GetResourceVersion())
		resp.SetItems(append(resp.GetItems(), page.GetItems()...))
	}
	return resp, nil
}

func (c *TestKindClient) Create(ctx context.Context, obj *TestKind, opts resource.CreateOptions) (*TestKind, error) {
	// Make sure apiVersion and kind are set
	obj.APIVersion = GroupVersion.Identifier()
	obj.Kind = TestKindKind().Kind()
	return c.client.Create(ctx, obj, opts)
}

func (c *TestKindClient) Update(ctx context.Context, obj *TestKind, opts resource.UpdateOptions) (*TestKind, error) {
	return c.client.Update(ctx, obj, opts)
}

func (c *TestKindClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*TestKind, error) {
	return c.client.Patch(ctx, identifier, req, opts)
}

func (c *TestKindClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus TestKindStatus, opts resource.UpdateOptions) (*TestKind, error) {
	return c.client.Update(ctx, &TestKind{
		TypeMeta: metav1.TypeMeta{
			Kind:       TestKindKind().Kind(),
			APIVersion: GroupVersion.Identifier(),
		},
		ObjectMeta: metav1.ObjectMeta{
			ResourceVersion: opts.ResourceVersion,
			Namespace:       identifier.Namespace,
			Name:            identifier.Name,
		},
		Status: newStatus,
	}, resource.UpdateOptions{
		Subresource:     "status",
		ResourceVersion: opts.ResourceVersion,
	})
}

func (c *TestKindClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
	return c.client.Delete(ctx, identifier, opts)
}

type CreateReconcileRequestRequest struct {
	Body    CreateReconcileRequestRequestBody
	Headers http.Header
}

func (c *TestKindClient) CreateReconcileRequest(ctx context.Context, identifier resource.Identifier, request CreateReconcileRequestRequest) (*CreateReconcileRequestResponse, error) {
	body, err := json.Marshal(request.Body)
	if err != nil {
		return nil, fmt.Errorf("unable to marshal body to JSON: %w", err)
	}
	resp, err := c.client.SubresourceRequest(ctx, identifier, resource.CustomRouteRequestOptions{
		Path:    "/reconcile",
		Verb:    "POST",
		Body:    io.NopCloser(bytes.NewReader(body)),
		Headers: request.Headers,
	})
	if err != nil {
		return nil, err
	}
	cast := CreateReconcileRequestResponse{}
	err = json.Unmarshal(resp, &cast)
	if err != nil {
		return nil, fmt.Errorf("unable to unmarshal response bytes into CreateReconcileRequestResponse: %w", err)
	}
	return &cast, nil
}

type GetTestKindSearchResultRequest struct {
	Params  GetTestKindSearchResultRequestParams
	Headers http.Header
}

func (c *TestKindClient) GetTestKindSearchResult(ctx context.Context, identifier resource.Identifier, request GetTestKindSearchResultRequest) (*GetTestKindSearchResultResponse, error) {
	params := url.Values{}
	params.Set("q", fmt.Sprintf("%v", request.Params.Q))
	resp, err := c.client.SubresourceRequest(ctx, identifier, resource.CustomRouteRequestOptions{
		Path:    "/search",
		Verb:    "GET",
		Query:   params,
		Headers: request.Headers,
	})
	if err != nil {
		return nil, err
	}
	cast := GetTestKindSearchResultResponse{}
	err = json.Unmarshal(resp, &cast)
	if err != nil {
		return nil, fmt.Errorf("unable to unmarshal response bytes into GetTestKindSearchResultResponse: %w", err)
	}
	return &cast, nil
}
