package service

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"os"
	"time"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/cluster-agent/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/cluster-service-ng/internal/cluster-agent/internal/sqlexec"
)

// v2HTTPDeps holds the resolved HTTP client and base URL for V2 methods.
type v2HTTPDeps struct {
	client  *http.Client
	baseURL string
}

// v2HTTPDepsResolverFunc resolves the HTTP client and base URL for V2 methods.
type v2HTTPDepsResolverFunc func(ctx context.Context, cli client.Client, clusterID string) (*v2HTTPDeps, error)

// defaultResolveV2HTTPDeps is the default implementation for resolving V2 HTTP dependencies.
var defaultResolveV2HTTPDeps v2HTTPDepsResolverFunc = func(ctx context.Context, cli client.Client, clusterID string) (*v2HTTPDeps, error) {
	namespace := os.Getenv("NAMESPACE")
	if namespace == "" {
		return nil, fmt.Errorf("NAMESPACE is empty")
	}

	sqlAddr, err := resolveTiDBAddr(ctx, cli, clusterID)
	if err != nil {
		return nil, fmt.Errorf("failed to resolve TiDB address: %w", err)
	}

	baseURL, err := sqlAddrToHTTPBaseURL(sqlAddr)
	if err != nil {
		return nil, fmt.Errorf("failed to convert SQL address to HTTP URL: %w", err)
	}

	tlsConfig, err := GetTLSConfig(ctx, cli, namespace, tlsClientSecret)
	if err != nil {
		return nil, fmt.Errorf("failed to get TLS config: %w", err)
	}
	// TiDB HTTP server presents the certificate from `db-tidb-cluster-secret`.
	// Its SANs cover `*.db-cluster.<namespace>.svc`, but do not include
	// `*.tidb<namespace>` or `*.tidb<namespace>.svc`.
	// Set ServerName explicitly so SNI/hostname verification matches the SAN pattern.
	tlsConfig = tlsConfig.Clone()
	tlsConfig.ServerName = fmt.Sprintf("tidb.db-cluster.%s.svc", namespace)

	return &v2HTTPDeps{
		client: &http.Client{
			Timeout: 30 * time.Second,
			Transport: &http.Transport{
				TLSClientConfig:       tlsConfig,
				TLSHandshakeTimeout:   15 * time.Second,
				ResponseHeaderTimeout: 15 * time.Second,
				IdleConnTimeout:       90 * time.Second,
			},
		},
		baseURL: baseURL,
	}, nil
}

// getV2HTTPDepsResolver returns the resolver function, using the injected one if set,
// or falling back to the default implementation.
func (s *AgentService) getV2HTTPDepsResolver() v2HTTPDepsResolverFunc {
	if s.resolveV2HTTPDeps != nil {
		return s.resolveV2HTTPDeps
	}
	return defaultResolveV2HTTPDeps
}

// DeleteCloudAdmin deletes the legacy cloud admin user restored from the dedicated cluster.
// It tries V2 (TiDB HTTP API) first. If the TiDB version doesn't support the API (404),
// it falls back to V1 (privileged TiDB with DROP USER).
func (s *AgentService) DeleteCloudAdmin(
	ctx context.Context, req *apiv1.DeleteCloudAdminReq) (*apiv1.DeleteCloudAdminResp, error) {
	logger := tilog.WithContextS(ctx).With("request", req)

	clusterID, err := s.validateDeleteCloudAdminRequest(req)
	if err != nil {
		logger.Errorf("failed to validate the request: %v", err)
		return nil, err
	}

	// V2-first: try HTTP API
	err = s.deleteCloudAdminViaHTTP(ctx, clusterID)
	if err == nil {
		logger.Infof("delete cloud admin via HTTP API succeeded")
		return &apiv1.DeleteCloudAdminResp{Message: "success"}, nil
	}

	// Only fallback on 404 (TiDB version doesn't support this API)
	var httpErr *TiDBHTTPError
	if !errors.As(err, &httpErr) || httpErr.StatusCode != http.StatusNotFound {
		msg := fmt.Sprintf("failed to delete cloud admin via TiDB HTTP API: %v", err)
		logger.Errorf(msg)
		return &apiv1.DeleteCloudAdminResp{Message: msg}, err
	}

	// V1 fallback: use privileged TiDB
	logger.Warnf("TiDB HTTP API returned 404, falling back to privileged TiDB: %v", err)

	s.mutexPrivilegedTiDBActivation.Lock()
	defer s.mutexPrivilegedTiDBActivation.Unlock()

	namespace := os.Getenv("NAMESPACE")
	lifecycle := &privilegedTiDBLifecycle{svc: s, namespace: namespace, enableAuditlog: true, username: "maintenance_admin"}
	executor := newPrivilegedTiDBExecutor(lifecycle)
	cmd := &sqlexec.DeleteCloudAdminCommand{}

	if _, err := executor.Execute(ctx, clusterID, cmd); err != nil {
		msg := logSQLError(logger, "failed to delete cloud admin user", err)
		return &apiv1.DeleteCloudAdminResp{Message: msg}, err
	}

	return &apiv1.DeleteCloudAdminResp{Message: "success"}, nil
}

// deleteCloudAdminViaHTTP deletes the cloud_admin user via TiDB Internal HTTP API (V2).
func (s *AgentService) deleteCloudAdminViaHTTP(ctx context.Context, clusterID string) error {
	deps, err := s.getV2HTTPDepsResolver()(ctx, s.dataplaneCli, clusterID)
	if err != nil {
		return fmt.Errorf("failed to resolve V2 HTTP dependencies: %w", err)
	}

	apiURL, err := buildTiDBInternalAPIURL(deps.baseURL, tidbInternalAPIPathDeleteCloudAdmin)
	if err != nil {
		return fmt.Errorf("failed to build TiDB internal API URL: %w", err)
	}

	httpReq, err := http.NewRequestWithContext(ctx, http.MethodDelete, apiURL, http.NoBody)
	if err != nil {
		return fmt.Errorf("failed to create HTTP request: %w", err)
	}

	return doTiDBHTTPRequest(deps.client, httpReq, http.StatusNoContent)
}

func (s *AgentService) validateDeleteCloudAdminRequest(req *apiv1.DeleteCloudAdminReq) (string, error) {
	if req == nil {
		return "", fmt.Errorf("request is nil")
	}
	return req.ClusterId, nil
}

// Wrap the constructor so tests can capture lifecycle arguments without modifying sqlexec.
var newPrivilegedTiDBExecutor = func(lifecycle sqlexec.PrivilegedLifecycle) sqlexec.Executor {
	return sqlexec.NewPrivilegedTiDBExecutor(lifecycle)
}
