package acscli

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

	"sigs.k8s.io/controller-runtime/pkg/log"
)

type Interface interface {
	ListNodeGroups(ctx context.Context, tenantID, projectID string) (*ListNodeGroupResponse, error)
	UpgradeNodeGroup(ctx context.Context, req *NodeGroupsUpgradeRequest) (*NodeGroupsUpgradeResponse, error)
}

type client struct {
	endpoint   string
	httpClient *http.Client
}

func NewClient(endpoint string) Interface {
	return &client{
		endpoint: endpoint,
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

func (c *client) buildURL(format string, args ...any) string {
	path := fmt.Sprintf(format, args...)
	return c.endpoint + path
}

func (c *client) doGet(ctx context.Context, rawURL string) (data []byte, err error) {
	startTime := time.Now()
	logger := log.FromContext(ctx)
	defer func() {
		cost := time.Since(startTime).String()
		preview := string(data)
		if len(preview) > 200 {
			preview = preview[:200] + "..."
		}
		if err == nil {
			logger.Info("acs GET request succeeded", "cost", cost, "url", rawURL, "response", preview)
		} else {
			logger.Error(err, "acs GET request failed", "cost", cost, "url", rawURL)
		}
	}()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
	if err != nil {
		return nil, err
	}
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err = io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= 300 {
		return nil, fmt.Errorf("acs GET %s: HTTP %d: %s", rawURL, resp.StatusCode, string(data))
	}
	return data, nil
}

func (c *client) doPost(ctx context.Context, rawURL string, body []byte) (data []byte, err error) {
	startTime := time.Now()
	logger := log.FromContext(ctx)
	defer func() {
		cost := time.Since(startTime).String()
		if err == nil {
			logger.Info("acs POST request succeeded", "cost", cost, "url", rawURL)
		} else {
			logger.Error(err, "acs POST request failed", "cost", cost, "url", rawURL)
		}
	}()

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, rawURL, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err = io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= 300 {
		return nil, fmt.Errorf("acs POST %s: HTTP %d: %s", rawURL, resp.StatusCode, string(data))
	}
	return data, nil
}

func (c *client) ListNodeGroups(ctx context.Context, tenantID, projectID string) (*ListNodeGroupResponse, error) {
	query := url.Values{}
	if tenantID != "" {
		query.Set("tenantId", tenantID)
	}
	if projectID != "" {
		query.Set("projectId", projectID)
	}
	rawURL := c.buildURL("/ops/nodegroups?%s", query.Encode())

	data, err := c.doGet(ctx, rawURL)
	if err != nil {
		return nil, fmt.Errorf("ListNodeGroups: %w", err)
	}

	out := new(ListNodeGroupResponse)
	if err := json.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}

func (c *client) UpgradeNodeGroup(ctx context.Context, req *NodeGroupsUpgradeRequest) (*NodeGroupsUpgradeResponse, error) {
	rawURL := c.buildURL("/ops/nodegroups/upgrade")
	bs, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}
	data, err := c.doPost(ctx, rawURL, bs)
	if err != nil {
		return nil, fmt.Errorf("UpgradeNodeGroup: %w", err)
	}

	out := new(NodeGroupsUpgradeResponse)
	if err := json.Unmarshal(data, out); err != nil {
		return nil, err
	}
	return out, nil
}
