package db

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

	"github.com/tidbcloud/controlplane9/internal/config"
	"github.com/tidbcloud/controlplane9/internal/verbose"
)

const (
	// zeroAPIBase is the TiDB Cloud Zero API base URL.
	zeroAPIBase = "https://zero.tidbapi.com/v1beta1"
)

// ZeroProvider provisions TiDB Cloud Zero instances.
// Zero is free, requires no auth, and returns a connectionString directly.
type ZeroProvider struct {
	// NamePrefix is the cp9 namespace prefix for resource naming (e.g. "cp9-alice").
	NamePrefix string
	// APIBase allows overriding the API URL for testing.
	APIBase string
	// Client allows injecting a custom HTTP client for testing.
	Client *http.Client
}

func (z *ZeroProvider) apiBase() string {
	if z.APIBase != "" {
		return z.APIBase
	}
	return zeroAPIBase
}

func (z *ZeroProvider) client() *http.Client {
	if z.Client != nil {
		return z.Client
	}
	return verbose.NewHTTPClient(30 * time.Second)
}

// Name returns the provider identifier.
func (z *ZeroProvider) Name() config.DatabaseProvider {
	return config.DBTiDBCloudZero
}

// zeroCreateRequest is the request body for creating a Zero instance.
type zeroCreateRequest struct {
	DisplayName string `json:"displayName"`
}

// zeroCreateResponse is the response from creating a Zero instance.
// The API wraps the instance in an "instance" envelope.
type zeroCreateResponse struct {
	Instance zeroInstance `json:"instance"`
}

// zeroInstance is the instance object within the Zero API response.
type zeroInstance struct {
	ID         string         `json:"id"`
	Connection zeroConnection `json:"connection"`
	ExpiresAt  string         `json:"expiresAt"`
}

// zeroConnection holds the structured connection details from the Zero API.
// The API also returns a masked connectionString field which is not usable.
type zeroConnection struct {
	Host     string `json:"host"`
	Port     int    `json:"port"`
	Username string `json:"username"`
	Password string `json:"password"`
}

// Provision creates a new TiDB Cloud Zero instance.
func (z *ZeroProvider) Provision(ctx context.Context) (*Instance, error) {
	displayName := z.NamePrefix + "-dev"
	body, err := json.Marshal(zeroCreateRequest{
		DisplayName: displayName,
	})
	if err != nil {
		return nil, fmt.Errorf("marshal zero request: %w", err)
	}

	url := z.apiBase() + "/instances"
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("create zero request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := z.client().Do(req)
	if err != nil {
		return nil, fmt.Errorf("zero API request: %w", err)
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read zero response: %w", err)
	}

	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
		return nil, fmt.Errorf("zero API returned %d: %s", resp.StatusCode, string(respBody))
	}

	var result zeroCreateResponse
	if err := json.Unmarshal(respBody, &result); err != nil {
		return nil, fmt.Errorf("parse zero response: %w", err)
	}

	conn := result.Instance.Connection
	if conn.Host == "" {
		return nil, fmt.Errorf("zero API returned empty connection host")
	}
	if conn.Port == 0 {
		return nil, fmt.Errorf("zero API returned zero connection port")
	}

	return &Instance{
		Provider: config.DBTiDBCloudZero,
		ID:       result.Instance.ID,
		Host:     conn.Host,
		Port:     conn.Port,
		User:     conn.Username,
		Password: conn.Password,
	}, nil
}

// CreateDatabase creates a named database within the Zero instance
// using the MySQL wire protocol.
func (z *ZeroProvider) CreateDatabase(ctx context.Context, inst *Instance, dbName string) error {
	return createDatabaseMySQL(ctx, inst, dbName)
}

// Destroy is a no-op for TiDB Cloud Zero — instances auto-recycle after a period
// of inactivity. The Zero API does not support explicit deletion (returns 501).
func (z *ZeroProvider) Destroy(_ context.Context, _ *Instance) error {
	return nil
}
