package devops

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/aisupport/services/jsm-syncer/config"
)

const defaultPageSize = 100

type Client interface {
	Configured() bool
	ListTenants(ctx context.Context, opts ListTenantsOptions) (TenantPage, error)
	GetTenant(ctx context.Context, tenantID string) (*Tenant, error)
}

type ListTenantsOptions struct {
	Page     int
	PageSize int
}

type TenantPage struct {
	Tenants []Tenant
	Total   int
}

type Tenant struct {
	ID        string
	Name      string
	Status    string
	TimeZone  string
	Plan      string
	BizRegion string
}

type httpClient struct {
	baseURL    string
	apiToken   string
	pageSize   int
	httpClient *http.Client
}

func NewClient(conf config.DevOpsConfig) Client {
	timeout := conf.Timeout
	if timeout <= 0 {
		timeout = 10 * time.Second
	}

	pageSize := conf.PageSize
	if pageSize <= 0 {
		pageSize = defaultPageSize
	}

	return &httpClient{
		baseURL:  strings.TrimRight(conf.BaseURL, "/"),
		apiToken: conf.APIToken,
		pageSize: pageSize,
		httpClient: &http.Client{
			Timeout: timeout,
		},
	}
}

func (c *httpClient) Configured() bool {
	return c != nil && c.baseURL != ""
}

func (c *httpClient) ListTenants(ctx context.Context, opts ListTenantsOptions) (TenantPage, error) {
	if !c.Configured() {
		return TenantPage{}, nil
	}

	page := opts.Page
	if page <= 0 {
		page = 1
	}
	pageSize := opts.PageSize
	if pageSize <= 0 {
		pageSize = c.pageSize
	}

	query := url.Values{}
	query.Set("page", strconv.Itoa(page))
	query.Set("per_page", strconv.Itoa(pageSize))

	var resp listTenantResponse
	if err := c.get(ctx, "/api/v1/devops-internal/tenants", query, &resp); err != nil {
		return TenantPage{}, err
	}

	tenants := make([]Tenant, 0, len(resp.Items))
	for _, item := range resp.Items {
		tenant := item.toTenant()
		if tenant.ID == "" {
			continue
		}
		tenants = append(tenants, tenant)
	}

	return TenantPage{
		Tenants: tenants,
		Total:   resp.Total,
	}, nil
}

func (c *httpClient) GetTenant(ctx context.Context, tenantID string) (*Tenant, error) {
	tenantID = strings.TrimSpace(tenantID)
	if tenantID == "" {
		return nil, fmt.Errorf("tenant_id is required")
	}
	if !c.Configured() {
		return nil, nil
	}

	var resp tenantResponse
	if err := c.get(ctx, "/api/v1/devops-internal/tenants/"+url.PathEscape(tenantID), nil, &resp); err != nil {
		return nil, err
	}
	tenant := resp.toTenant()
	if tenant.ID == "" {
		return nil, nil
	}
	return &tenant, nil
}

func (c *httpClient) get(ctx context.Context, path string, query url.Values, result any) error {
	u := c.baseURL + path
	if len(query) > 0 {
		u += "?" + query.Encode()
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Accept", "application/json")
	if c.apiToken != "" {
		req.Header.Set("X-API-TOKEN", c.apiToken)
		req.Header.Set("X-API-KEY", c.apiToken)
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("devops get %s: %w", path, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
		return fmt.Errorf("devops get %s failed: %s", path, resp.Status)
	}
	if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
		return fmt.Errorf("decode devops response %s: %w", path, err)
	}
	return nil
}

type listTenantResponse struct {
	Items []tenantResponse `json:"items"`
	Total int              `json:"total"`
}

type tenantResponse struct {
	ID               string `json:"id"`
	Name             string `json:"name"`
	Status           string `json:"status"`
	TimeZone         string `json:"time_zone"`
	Plan             string `json:"plan"`
	Region           string `json:"region"`
	CountryRegion    string `json:"country_region"`
	SalesForceRegion string `json:"sales_force_region"`
}

func (r tenantResponse) toTenant() Tenant {
	return Tenant{
		ID:        strings.TrimSpace(r.ID),
		Name:      strings.TrimSpace(r.Name),
		Status:    strings.TrimSpace(r.Status),
		TimeZone:  strings.TrimSpace(r.TimeZone),
		Plan:      strings.TrimSpace(r.Plan),
		BizRegion: firstNonEmpty(r.Region, r.SalesForceRegion, r.CountryRegion),
	}
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value != "" {
			return value
		}
	}
	return ""
}
