package tenant

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math/big"
	"regexp"
	"strings"

	"github.com/mem9-ai/drive9/pkg/meta"
)

var tiDBCloudBillingDecimalPattern = regexp.MustCompile(`^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)$`)

var (
	ErrTiDBCloudBillingUnavailable         = errors.New("tidbcloud billing plan lookup unavailable")
	ErrTiDBCloudBillingResponseInvalid     = errors.New("tidbcloud billing plan response invalid")
	ErrTiDBCloudFreeQuotaMutationForbidden = errors.New("quota updates are not available for free TiDB Cloud organizations")
	ErrTiDBCloudFreeTenantPoolForbidden    = errors.New("tenant pools are not available for free TiDB Cloud organizations")
	ErrTiDBCloudFreeSpendingLimitForbidden = errors.New("positive spending limit is not available for free TiDB Cloud organizations")
	ErrTiDBCloudFreeQuotaExceeded          = errors.New("requested quota exceeds the free TiDB Cloud organization limit")
	ErrTiDBCloudFreeTenantLimitReached     = meta.ErrTiDBCloudFreeTenantLimitReached
	ErrTiDBCloudFreeQuotaBusy              = meta.ErrTiDBCloudFreeQuotaBusy
)

type TiDBCloudOrganizationPlan struct {
	OrganizationID string
	EffectivePlan  string
	POCCredits     string
	IsFree         bool
}

type TiDBCloudOrganizationPlanResolver interface {
	ResolveOrganizationPlan(ctx context.Context, organizationID string, req CredentialProvisionRequest) (*TiDBCloudOrganizationPlan, error)
}

// ParseTiDBCloudOrganizationPlan validates and classifies a Billing plan response for one organization.
func ParseTiDBCloudOrganizationPlan(raw []byte, organizationID string) (*TiDBCloudOrganizationPlan, error) {
	var response struct {
		TenantID      json.RawMessage `json:"tenant_id"`
		TenantIDStr   string          `json:"tenant_id_str"`
		EffectivePlan string          `json:"effective_plan"`
		POCCredits    string          `json:"poc_credits"`
	}
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	if err := decoder.Decode(&response); err != nil {
		return nil, fmt.Errorf("%w: malformed JSON", ErrTiDBCloudBillingResponseInvalid)
	}
	if decoder.Decode(&struct{}{}) != io.EOF {
		return nil, fmt.Errorf("%w: trailing JSON", ErrTiDBCloudBillingResponseInvalid)
	}

	identities := make([]string, 0, 2)
	if value := strings.TrimSpace(response.TenantIDStr); value != "" {
		identities = append(identities, value)
	}
	if len(response.TenantID) > 0 && string(response.TenantID) != "null" {
		var number json.Number
		if err := json.Unmarshal(response.TenantID, &number); err != nil || strings.TrimSpace(number.String()) == "" {
			return nil, fmt.Errorf("%w: invalid numeric organization", ErrTiDBCloudBillingResponseInvalid)
		}
		identities = append(identities, number.String())
	}
	if len(identities) == 0 {
		return nil, fmt.Errorf("%w: organization is missing", ErrTiDBCloudBillingResponseInvalid)
	}
	for _, identity := range identities {
		if identity != organizationID {
			return nil, fmt.Errorf("%w: organization mismatch", ErrTiDBCloudBillingResponseInvalid)
		}
	}

	effectivePlan := strings.TrimSpace(response.EffectivePlan)
	isFree := true
	switch effectivePlan {
	case "on_demand":
		isFree = false
	case "poc":
		credits := strings.TrimSpace(response.POCCredits)
		if !tiDBCloudBillingDecimalPattern.MatchString(credits) {
			return nil, fmt.Errorf("%w: invalid POC credits", ErrTiDBCloudBillingResponseInvalid)
		}
		value, ok := new(big.Rat).SetString(credits)
		if !ok {
			return nil, fmt.Errorf("%w: invalid POC credits", ErrTiDBCloudBillingResponseInvalid)
		}
		isFree = value.Sign() <= 0
	}
	return &TiDBCloudOrganizationPlan{
		OrganizationID: organizationID,
		EffectivePlan:  effectivePlan,
		POCCredits:     strings.TrimSpace(response.POCCredits),
		IsFree:         isFree,
	}, nil
}
