package service

import (
	"context"
	"fmt"
	"log/slog"

	"github.com/tidbcloud/config-center/internal/db"
	pb "github.com/tidbcloud/config-center/proto/config_center"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func GetComponentImage(d db.DB, req *pb.GetComponentImageRequest) (*pb.ComponentImage, error) {
	tx, err := d.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	baseImage, tag, err := tx.GetComponentImage(req.GetClusterType().String(), req.GetComponentName(), req.GetComponentVersion(), req.GetTenantId())
	if err != nil && err != db.ErrNotFound {
		return nil, err
	} else if err == nil {
		return &pb.ComponentImage{
			BaseImage: baseImage,
			Tag:       tag,
		}, tx.Commit()
	}

	return nil, status.Errorf(codes.NotFound, "image info of component %s not found", req.GetComponentName())
}

func ListComponentImages(d db.DB, req *pb.ListComponentImagesRequest) (*pb.ListComponentImagesResponse, error) {
	tx, err := d.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	resp := &pb.ListComponentImagesResponse{}
	rows, err := tx.ListComponentImages(req.GetClusterType().String(), req.GetComponentName(), req.GetVersion())
	if err != nil {
		return nil, status.Error(codes.Internal, fmt.Sprintf("failed to list component images: %v", err))
	}

	for _, row := range rows {
		resp.Images = append(resp.Images,
			&pb.ComponentImage{
				ComponentName:       row.ComponentName,
				Version:             row.Version,
				BaseImage:           row.BaseImage,
				Tag:                 row.Tag,
				GeneralAvailability: row.GA,
			},
		)
	}
	return resp, tx.Commit()
}

// todo: rename to forceupdate
func (s *ConfigCenterService) UpdateComponentImage(req *pb.UpdateComponentImageRequest) (*pb.ComponentImage, error) {
	slog.Info("UpdateComponentImage called", "cluster_type", req.GetClusterType(), "component", req.GetComponentName(), "version", req.GetComponentVersion(), "tenant_id", req.GetTenantId())
	tx, err := s.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	err = tx.InsertComponentImage(req.GetClusterType().String(), req.GetComponentName(), req.GetComponentVersion(), req.GetBaseImage(), req.GetTag(), req.GetTenantId())
	if err == db.ErrAlreadyExists {
		err = tx.UpdateComponentImage(req.GetClusterType().String(), req.GetComponentName(), req.GetComponentVersion(), req.GetBaseImage(), req.GetTag(), req.GetTenantId())
		if err != nil {
			return nil, err
		}
	} else if err != nil {
		return nil, err
	}

	for region, client := range s.GetRegionalClients(req.GetClusterType()) {
		slog.Info("update component image for regional service", "region", region, "component", req.GetComponentName(), "version", req.GetComponentVersion(), "tenant_id", req.GetTenantId())
		_, err := client.UpdateComponentImage(context.Background(), req)
		if err != nil {
			slog.Error("failed to finish calling regional UpdateComponentImage", "region", region, "error", err)
		}
	}

	return &pb.ComponentImage{
		BaseImage: req.GetBaseImage(),
		Tag:       req.GetTag(),
	}, tx.Commit()

}

func GetDefaultVersion(d db.DB, req *pb.GetDefaultVersionRequest) (*pb.DefaultVersion, error) {
	tx, err := d.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	component := req.GetComponent()
	if req.GetClusterType() == pb.ClusterType_DEDICATED_TIDB && component == "" {
		component = "tidb"
	}

	/* rule priority:
	1. "tenant": (component, tenant id) => component version
	2. "cluster_version": (component, cluster version) => component version
	3. "region": (compoment, cloud provider, region, cluster version) => component version
	4. "cloud_provider": (component, cloud provider, cluster version) => component version
	5. "global": component => version
	*/

	if req.TenantId != nil && *req.TenantId != 0 {
		version, err := tx.GetDefaultVersion(req.GetClusterType().String(), component, "", "", "", req.GetTenantId())
		if err != nil && err != db.ErrNotFound {
			return nil, err
		} else if err == nil {
			return &pb.DefaultVersion{
				Version: version,
				Source:  "tenant",
			}, tx.Commit()
		}
	}

	if req.GetClusterVersion() != "" {
		version, err := tx.GetDefaultVersion(req.GetClusterType().String(), component, req.GetClusterVersion(), "", "", 0)
		if err != nil && err != db.ErrNotFound {
			return nil, err
		} else if err == nil {
			return &pb.DefaultVersion{
				Version: version,
				Source:  "cluster_version",
			}, tx.Commit()
		}
	}

	if req.Region != nil {
		version, err := tx.GetDefaultVersion(req.GetClusterType().String(), component, req.GetClusterVersion(), req.GetCloudProvider(), req.GetRegion(), 0)
		if err != nil && err != db.ErrNotFound {
			return nil, err
		} else if err == nil {
			return &pb.DefaultVersion{
				Version: version,
				Source:  "region",
			}, tx.Commit()
		}
	}

	if req.CloudProvider != nil {
		version, err := tx.GetDefaultVersion(req.GetClusterType().String(), component, req.GetClusterVersion(), req.GetCloudProvider(), "", 0)
		if err != nil && err != db.ErrNotFound {
			return nil, err
		} else if err == nil {
			return &pb.DefaultVersion{
				Version: version,
				Source:  "cloud_provider",
			}, tx.Commit()
		}
	}

	version, err := tx.GetDefaultVersion(req.GetClusterType().String(), component, "", "", "", 0)
	if err != nil && err != db.ErrNotFound {
		return nil, err
	} else if err == nil {
		return &pb.DefaultVersion{
			Version: version,
			Source:  "global",
		}, tx.Commit()
	}

	return nil, status.Errorf(codes.NotFound, "default version not found")
}

func (s *ConfigCenterService) UpdateDefaultVersion(req *pb.UpdateDefaultVersionRequest) (*pb.DefaultVersion, error) {
	tx, err := s.DB.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	component := req.GetComponent()
	if req.GetClusterType() == pb.ClusterType_DEDICATED_TIDB && component == "" {
		component = "tidb"
	}
	clusterVersio := req.GetClusterVersion()
	cloudProvider := req.GetCloudProvider()
	region := req.GetRegion()
	tenantId := req.GetTenantId()
	if tenantId != 0 {
		cloudProvider = ""
		region = "" // check: ignore cloud and region
	}
	version := req.GetVersion()

	_, err = tx.GetDefaultVersion(req.GetClusterType().String(), component, clusterVersio, cloudProvider, region, tenantId)
	switch err {
	case nil:
		err = tx.UpdateDefaultVersion(req.GetClusterType().String(), component, clusterVersio, cloudProvider, region, tenantId, version)
	case db.ErrNotFound:
		err = tx.InsertDefaultVersion(req.GetClusterType().String(), component, clusterVersio, cloudProvider, region, tenantId, version)
	}
	if err != nil {
		return nil, err
	}

	for region, client := range s.GetRegionalClients(req.GetClusterType()) {
		_, err := client.UpdateDefaultVersion(context.Background(), req)
		if err != nil {
			slog.Error("failed to finish calling regional UpdateDefaultVersion", "region", region, "error", err)
		}
	}

	return &pb.DefaultVersion{
		Version: version,
	}, tx.Commit()
}

func DeleteDefaultVersion(d db.DB, req *pb.DeleteDefaultVersionRequest) error {
	tx, err := d.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	component := req.GetComponent()
	if req.GetClusterType() == pb.ClusterType_DEDICATED_TIDB && component == "" {
		component = "tidb"
	}
	clusterVersion := req.GetClusterVersion()
	cloudProvider := req.GetCloudProvider()
	region := req.GetRegion()
	tenantId := req.GetTenantId()
	if tenantId != 0 {
		cloudProvider = ""
		region = "" // check: ignore cloud and region
	}

	err = tx.DeleteDefaultVersion(req.GetClusterType().String(), component, clusterVersion, cloudProvider, region, tenantId)
	if err != nil {
		return err
	}

	return tx.Commit()
}

func ListDefaultVersionRules(d db.DB, req *pb.ListDefaultVersionRulesRequest) (*pb.ListDefaultVersionRulesResponse, error) {
	tx, err := d.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	rules, err := tx.ListDefaultVersionRules(req.GetClusterType().String(), req.Component, req.TenantId)
	if err != nil {
		return nil, err
	}

	resp := &pb.ListDefaultVersionRulesResponse{Rules: []*pb.DefaultVersionRule{}}
	for _, r := range rules {
		resp.Rules = append(resp.Rules, &pb.DefaultVersionRule{
			ClusterType:    pb.ClusterType(pb.ClusterType_value[r.ClusterType]),
			Component:      r.ComponentName,
			CloudProvider:  r.CloudProvider,
			Region:         r.Region,
			ClusterVersion: r.ClusterVersion,
			Version:        r.Version,
		})
	}

	return resp, tx.Commit()
}
