package service

import (
	"context"
	dto "dataservices/internal/dto/dataapi_control"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/miekg/dns"
	customdomainv1 "github.com/tidbcloud/api-gateway/operator/api/v1"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
	targetNamespace   = "dataservice"
	targetServiceName = "dataservice-dataapp-gateway-service"
	targetServicePort = 11000
)

var kubeClient client.Client

func InitClient(cfg *rest.Config) error {
	scheme := runtime.NewScheme()
	utilruntime.Must(customdomainv1.AddToScheme(scheme))
	var err error
	kubeClient, err = client.New(cfg, client.Options{Scheme: scheme})
	return err
}

func KubeCreateCustomDomain(ctx context.Context, host, cname string) (*dto.KubeCustomDomainResponse, errs.Error) {
	var domain customdomainv1.CustomDomain
	err := kubeClient.Get(ctx, client.ObjectKey{Name: host, Namespace: targetNamespace}, &domain)
	if err == nil {
		state, message := extractCustomDomainState(ctx, &domain)
		return &dto.KubeCustomDomainResponse{
			BaseResp: dto.BaseResp{Code: http.StatusOK, Message: message},
			State:    state,
		}, nil
	}
	newDomain := customdomainv1.CustomDomain{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: targetNamespace,
			Name:      host,
		},
		Spec: customdomainv1.CustomDomainSpec{
			Host:        host,
			Cname:       cname,
			ServiceName: targetServiceName,
			ServicePort: targetServicePort,
		},
	}
	if err := kubeClient.Create(ctx, &newDomain); err != nil {
		return nil, errs.NewInternalHTTP(fmt.Sprintf("failed to create customdomain in k8s: %s", err), err)
	}
	return &dto.KubeCustomDomainResponse{
		BaseResp: dto.BaseResp{Code: http.StatusOK},
		State:    dto.CustomDomainStatePending,
	}, nil
}

func KubeGetCustomDomain(ctx context.Context, host string) (*dto.KubeCustomDomainResponse, errs.Error) {
	var domain customdomainv1.CustomDomain
	if err := kubeClient.Get(ctx, client.ObjectKey{Name: host, Namespace: targetNamespace}, &domain); err != nil {
		return nil, errs.NewInternalHTTP(fmt.Sprintf("failed to get customdomain in k8s: %s", err), err)
	}
	state, message := extractCustomDomainState(ctx, &domain)
	return &dto.KubeCustomDomainResponse{
		BaseResp: dto.BaseResp{Code: http.StatusOK, Message: message},
		State:    state,
	}, nil
}

func KubeDeleteCustomDomain(ctx context.Context, host string) errs.Error {
	domain := customdomainv1.CustomDomain{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: targetNamespace,
			Name:      host,
		},
	}
	if err := kubeClient.Delete(ctx, &domain); err != nil {
		return errs.NewInternalHTTP(fmt.Sprintf("failed to delete customdomain in k8s: %s", err), err)
	}
	return nil
}

func extractCustomDomainState(ctx context.Context, domain *customdomainv1.CustomDomain) (dto.CustomDomainState, string) {
	for _, cond := range domain.Status.Conditions {
		if cond.Type == customdomainv1.CustomDomainConditionReady && cond.Status == customdomainv1.ConditionTrue {
			// If DNS removed by user, change state to failure
			// ok, _, err := isDnsConfigured(ctx, domain.Spec.Host, domain.Spec.Cname)
			// if err == nil && !ok {
			// 	return dto.CustomDomainStateFailure, "The DNS check has failed, indicating the DNS record might have been removed from your DNS provider. Please verify your DNS settings."
			// }
			return dto.CustomDomainStateSuccess, ""
		}
	}
	return dto.CustomDomainStatePending, ""
}

func isDnsConfigured(ctx context.Context, host, target string) (bool, time.Duration, error) {
	dnsConfig, err := dns.ClientConfigFromFile("/etc/resolv.conf")
	if err != nil {
		return false, 0, err
	}
	m := new(dns.Msg)
	m.SetQuestion(dns.Fqdn(host), dns.TypeCNAME)
	c := new(dns.Client)
	in, rtt, err := c.Exchange(m, net.JoinHostPort(dnsConfig.Servers[0], dnsConfig.Port))
	if err != nil {
		return false, rtt, err
	}
	tilog.Info(ctx, "dns result", zap.String("host", host), zap.String("target", target), zap.Any("answers", in.Answer), zap.Any("rtt", rtt))
	if len(in.Answer) == 0 || !strings.Contains(in.Answer[0].String(), target) {
		return false, rtt, nil
	}
	return true, rtt, nil
}
