package dbaasui

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

// DedicatedCIDRReceipt records the safe scope and ownership result of the
// project-region CIDR preparation that precedes a public Dedicated create.
type DedicatedCIDRReceipt struct {
	Action             string
	Baseline           string
	CIDR               string
	EffectiveOrgID     string
	EffectiveProjectID string
	ID                 string
	Provider           string
	Region             string
	RequestedOrgID     string
	RequestedProjectID string
}

// Summary returns a credential-free receipt suitable for a workflow summary.
func (r *DedicatedCIDRReceipt) Summary() string {
	return fmt.Sprintf(
		"requested_org=%s requested_project=%s effective_org=%s effective_project=%s provider=%s region=%s cidr=%s baseline=%s action=%s id=%s",
		r.RequestedOrgID,
		r.RequestedProjectID,
		r.EffectiveOrgID,
		r.EffectiveProjectID,
		r.Provider,
		r.Region,
		r.CIDR,
		r.Baseline,
		r.Action,
		r.ID,
	)
}

type dedicatedCIDRRegion struct {
	Name     string `json:"name"`
	Provider string `json:"provider"`
}

type dedicatedCIDRRecord struct {
	ID        string              `json:"id"`
	ProjectID string              `json:"project_id"`
	CIDR      string              `json:"cidr"`
	Provider  string              `json:"provider"`
	State     string              `json:"state"`
	Region    dedicatedCIDRRegion `json:"region"`
}

type dedicatedCIDRListResponse struct {
	Items []dedicatedCIDRRecord `json:"items"`
}

type dedicatedCIDRResponseEnvelope struct {
	BaseResp json.RawMessage `json:"base_resp"`
}

func dedicatedCIDRApplicationError(response []byte) error {
	var envelope dedicatedCIDRResponseEnvelope
	if err := json.Unmarshal(response, &envelope); err != nil {
		return nil
	}
	baseResp := strings.TrimSpace(string(envelope.BaseResp))
	if baseResp == "" || baseResp == "null" {
		return nil
	}
	var fields map[string]json.RawMessage
	if err := json.Unmarshal(envelope.BaseResp, &fields); err != nil {
		return fmt.Errorf("dedicated CIDR API error: malformed base_resp")
	}
	rawCode, ok := fields["err_code"]
	if !ok {
		return nil
	}
	var errCode string
	if err := json.Unmarshal(rawCode, &errCode); err != nil {
		return fmt.Errorf("dedicated CIDR API error: malformed err_code")
	}
	errCode = strings.TrimSpace(errCode)
	if errCode == "" {
		return nil
	}
	code, err := strconv.ParseInt(errCode, 10, 64)
	if err != nil {
		return fmt.Errorf("dedicated CIDR API error: malformed err_code")
	}
	if code == 0 {
		return nil
	}
	rawMessage, hasMessage := fields["err_msg"]
	var messageValue string
	if hasMessage {
		_ = json.Unmarshal(rawMessage, &messageValue)
	}
	message := strings.Join(strings.Fields(messageValue), " ")
	if runes := []rune(message); len(runes) > 256 {
		message = string(runes[:256])
	}
	if message == "" {
		return fmt.Errorf("dedicated CIDR API error code=%d", code)
	}
	return fmt.Errorf("dedicated CIDR API error code=%d message=%q", code, message)
}

// PrepareDedicatedCIDR mirrors the shipped dbaas-ui project-region setup:
// list first, create once only when absent, and read back the exact created ID.
// CIDRs are persistent project-region state; this helper never deletes them.
func PrepareDedicatedCIDR(
	ctx context.Context,
	client *http.Client,
	baseURL string,
	orgID string,
	projectID string,
	provider string,
	region string,
	cidr string,
	bearerToken string,
) (DedicatedCIDRReceipt, error) {
	if client == nil {
		return DedicatedCIDRReceipt{}, fmt.Errorf("dedicated CIDR HTTP client is required")
	}
	listPath := dedicatedCIDRListPath(orgID, projectID, provider)
	createPath := dedicatedCIDRCreatePath(orgID, projectID)
	list, err := listDedicatedCIDRs(ctx, client, baseURL, listPath, bearerToken)
	if err != nil {
		return DedicatedCIDRReceipt{}, fmt.Errorf("list Dedicated CIDRs: %w", err)
	}
	if existing, ok := findDedicatedCIDR(list.Items, projectID, provider, region); ok {
		return DedicatedCIDRReceipt{
			Action:             "reused",
			Baseline:           "existing",
			CIDR:               existing.CIDR,
			EffectiveOrgID:     orgID,
			EffectiveProjectID: existing.ProjectID,
			ID:                 existing.ID,
			Provider:           provider,
			Region:             region,
			RequestedOrgID:     orgID,
			RequestedProjectID: projectID,
		}, nil
	}

	createBody, err := json.Marshal(struct {
		CIDR   string              `json:"cidr"`
		Region dedicatedCIDRRegion `json:"region"`
	}{
		CIDR:   cidr,
		Region: dedicatedCIDRRegion{Name: region, Provider: strings.ToUpper(provider)},
	})
	if err != nil {
		return DedicatedCIDRReceipt{}, fmt.Errorf("encode Dedicated CIDR request: %w", err)
	}
	status, response, err := performDedicatedCIDRRequest(
		ctx,
		client,
		baseURL,
		http.MethodPost,
		createPath,
		createBody,
		bearerToken,
	)
	if err != nil {
		return DedicatedCIDRReceipt{}, fmt.Errorf("create Dedicated CIDR: %w", err)
	}
	if status != http.StatusOK {
		return DedicatedCIDRReceipt{}, fmt.Errorf("create Dedicated CIDR returned HTTP %d", status)
	}
	if err := dedicatedCIDRApplicationError(response); err != nil {
		return DedicatedCIDRReceipt{}, fmt.Errorf("create Dedicated CIDR: %w", err)
	}
	var created struct {
		ID string `json:"id"`
	}
	if err := json.Unmarshal(response, &created); err != nil {
		return DedicatedCIDRReceipt{}, fmt.Errorf("decode Dedicated CIDR create response: %w", err)
	}
	if strings.TrimSpace(created.ID) == "" {
		return DedicatedCIDRReceipt{}, fmt.Errorf("dedicated CIDR create response has no id")
	}

	list, err = listDedicatedCIDRs(ctx, client, baseURL, listPath, bearerToken)
	if err != nil {
		return DedicatedCIDRReceipt{}, fmt.Errorf("read back Dedicated CIDRs: %w", err)
	}
	createdRecord, ok := findDedicatedCIDR(list.Items, projectID, provider, region)
	if !ok || createdRecord.ID != created.ID || createdRecord.CIDR != cidr {
		return DedicatedCIDRReceipt{}, fmt.Errorf(
			"dedicated CIDR create did not read back exact project=%s provider=%s region=%s cidr=%s id=%s",
			projectID,
			provider,
			region,
			cidr,
			created.ID,
		)
	}
	return DedicatedCIDRReceipt{
		Action:             "created",
		Baseline:           "absent",
		CIDR:               createdRecord.CIDR,
		EffectiveOrgID:     orgID,
		EffectiveProjectID: createdRecord.ProjectID,
		ID:                 createdRecord.ID,
		Provider:           provider,
		Region:             region,
		RequestedOrgID:     orgID,
		RequestedProjectID: projectID,
	}, nil
}

func listDedicatedCIDRs(ctx context.Context, client *http.Client, baseURL, path, bearerToken string) (dedicatedCIDRListResponse, error) {
	status, response, err := performDedicatedCIDRRequest(ctx, client, baseURL, http.MethodGet, path, nil, bearerToken)
	if err != nil {
		return dedicatedCIDRListResponse{}, err
	}
	if status != http.StatusOK {
		return dedicatedCIDRListResponse{}, fmt.Errorf("list Dedicated CIDRs returned HTTP %d", status)
	}
	if err := dedicatedCIDRApplicationError(response); err != nil {
		return dedicatedCIDRListResponse{}, err
	}
	var list dedicatedCIDRListResponse
	if err := json.Unmarshal(response, &list); err != nil {
		return dedicatedCIDRListResponse{}, fmt.Errorf("decode Dedicated CIDR list response: %w", err)
	}
	return list, nil
}

func dedicatedCIDRListPath(orgID, projectID, provider string) string {
	query := url.Values{"provider": []string{strings.ToUpper(provider)}}
	return fmt.Sprintf(
		"/api/v1/orgs/%s/projects/%s/cidrs?%s",
		url.PathEscape(orgID),
		url.PathEscape(projectID),
		query.Encode(),
	)
}

func dedicatedCIDRCreatePath(orgID, projectID string) string {
	return fmt.Sprintf(
		"/api/v1/orgs/%s/projects/%s/cidrs",
		url.PathEscape(orgID),
		url.PathEscape(projectID),
	)
}

func findDedicatedCIDR(items []dedicatedCIDRRecord, projectID, provider, region string) (dedicatedCIDRRecord, bool) {
	for _, item := range items {
		if item.ProjectID != projectID || !strings.EqualFold(item.Region.Name, region) {
			continue
		}
		itemProvider := item.Provider
		if itemProvider == "" {
			itemProvider = item.Region.Provider
		}
		if !strings.EqualFold(itemProvider, provider) || strings.TrimSpace(item.CIDR) == "" || strings.TrimSpace(item.ID) == "" {
			continue
		}
		return item, true
	}
	return dedicatedCIDRRecord{}, false
}

func performDedicatedCIDRRequest(
	ctx context.Context,
	client *http.Client,
	baseURL string,
	method string,
	path string,
	body []byte,
	bearerToken string,
) (int, []byte, error) {
	base, err := url.Parse(baseURL)
	if err != nil {
		return 0, nil, fmt.Errorf("parse API base URL %q: %w", baseURL, err)
	}
	relative, err := url.Parse(path)
	if err != nil {
		return 0, nil, fmt.Errorf("parse API path %q: %w", path, err)
	}
	endpoint := base.ResolveReference(relative)
	request, err := http.NewRequestWithContext(ctx, method, endpoint.String(), bytes.NewReader(body))
	if err != nil {
		return 0, nil, fmt.Errorf("build API request %s %s: %w", method, path, err)
	}
	request.Header.Set("Accept", "application/json")
	if strings.TrimSpace(bearerToken) != "" {
		request.Header.Set("Authorization", "Bearer "+strings.TrimSpace(bearerToken))
	}
	if body != nil {
		request.Header.Set("Content-Type", "application/json")
	}
	response, err := client.Do(request)
	if err != nil {
		return 0, nil, fmt.Errorf("API request %s %s: %w", method, path, err)
	}
	defer response.Body.Close()
	responseBody, err := io.ReadAll(response.Body)
	if err != nil {
		return response.StatusCode, nil, fmt.Errorf("read API response %s %s: %w", method, path, err)
	}
	return response.StatusCode, responseBody, nil
}
