package opsservice

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	helmv2 "github.com/fluxcd/helm-controller/api/v2"
	"github.com/fluxcd/pkg/apis/meta"
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/errors"
	"github.com/tidbcloud/observability/pkg/common/message"
	"github.com/tidbcloud/observability/pkg/resourcemanager"
)

var (
	helmReleaseSvc     *HelmReleaseService
	hrOnce             sync.Once
	basicAuthConfigErr = fmt.Errorf("basic auth info not present in values")
)

func GetHelmReleaseService() *HelmReleaseService {
	hrOnce.Do(func() {
		if helmReleaseSvc == nil {
			helmReleaseSvc = &HelmReleaseService{
				helmReleaseRW: resourcemanager.GetHelmReleaseReaderWriter(),
				diffRunner:    newCommandHelmReleaseDiffRunner(),
			}
		}
	})
	return helmReleaseSvc
}

// HelmReleaseService provides operations for managing HelmRelease CRs
type HelmReleaseService struct {
	helmReleaseRW resourcemanager.HelmReleaseReaderWriter
	diffRunner    helmReleaseDiffRunner
}

func stampHelmReleaseReconcileRequest(hr *helmv2.HelmRelease) {
	if hr.Annotations == nil {
		hr.Annotations = map[string]string{}
	}

	token := strconv.FormatInt(time.Now().UTC().UnixNano(), 10)
	hr.Annotations[meta.ReconcileRequestAnnotation] = token
	hr.Annotations[helmv2.ResetRequestAnnotation] = token
}

// CreateHelmRelease creates a new HelmRelease CR
func (s *HelmReleaseService) CreateHelmRelease(ctx context.Context, req message.CreateHelmReleaseReq) (message.CreateHelmReleaseResp, error) {
	// Convert simplified API request directly to FluxCD HelmRelease CR
	fluxHelmRelease, err := message.ConvertToFluxCDHelmRelease(req.Name, req.Namespace, req.Component, req.Config)
	if err != nil {
		return message.CreateHelmReleaseResp{}, fmt.Errorf("failed to convert to FluxCD HelmRelease: %w", err)
	}
	stampHelmReleaseReconcileRequest(fluxHelmRelease)

	// Create the HelmRelease CR
	err = s.helmReleaseRW.Create(ctx, fluxHelmRelease)
	if err != nil {
		return message.CreateHelmReleaseResp{}, fmt.Errorf("failed to create HelmRelease: %w", err)
	}

	// Return response with initial status
	now := time.Now()
	resp := message.CreateHelmReleaseResp{
		Name:      req.Name,
		Namespace: req.Namespace,
		Component: req.Component,
		Config:    req.Config,
		Status: message.HelmReleaseStatusSummary{
			ObservedGeneration: 1,
			Failures:           0,
		},
		CreateTime: now,
	}

	return resp, nil
}

// GetHelmRelease retrieves details of a specific HelmRelease CR
func (s *HelmReleaseService) GetHelmRelease(ctx context.Context, req message.GetHelmReleaseReq) (message.GetHelmReleaseResp, error) {
	// Get HelmRelease CR from K8s
	hr, err := s.helmReleaseRW.Get(ctx, constants.OpsSystemNamespace, req.Name)
	if err != nil {
		if errors.Is(err, errors.HelmReleaseNotFound) {
			return message.GetHelmReleaseResp{}, err
		}
		return message.GetHelmReleaseResp{}, fmt.Errorf("failed to get HelmRelease: %w", err)
	}

	// Convert FluxCD HelmRelease to simplified API format
	component, config := message.ConvertFromFluxCDHelmRelease(hr)

	// Convert to response format
	resp := message.GetHelmReleaseResp{
		Name:       hr.Name,
		Namespace:  hr.Spec.TargetNamespace,
		Component:  component,
		Config:     config,
		Status:     message.ConvertFluxCDStatusToSummary(hr.Status),
		CreateTime: hr.CreationTimestamp.Time,
	}

	return resp, nil
}

// ListHelmReleases lists all HelmRelease CRs
func (s *HelmReleaseService) ListHelmReleases(ctx context.Context, req message.ListHelmReleasesReq) (message.ListHelmReleasesResp, error) {
	var hrList *helmv2.HelmReleaseList
	var err error

	// Create pagination request
	cr := req.ContinueRequest

	// List HelmReleases either in specific namespace or all namespaces
	hrList, err = s.helmReleaseRW.List(ctx, constants.OpsSystemNamespace, cr)
	if err != nil {
		return message.ListHelmReleasesResp{}, fmt.Errorf("failed to list HelmReleases: %w", err)
	}

	// Convert to summary format
	summaries := make([]message.HelmReleaseSummary, 0, len(hrList.Items))
	for _, fluxHR := range hrList.Items {
		// Convert FluxCD HelmRelease to simplified API format
		component, config := message.ConvertFromFluxCDHelmRelease(&fluxHR)

		summary := message.HelmReleaseSummary{
			Name:      fluxHR.Name,
			Namespace: fluxHR.Namespace,
			Component: component,
			Config: message.HelmReleaseConfigSummary{
				TargetNamespace: fluxHR.Namespace,
				Suspend:         config.Suspend,
				DependsOn:       config.DependsOn,
			},
			Status:     message.ConvertFluxCDStatusToSummary(fluxHR.Status),
			CreateTime: fluxHR.CreationTimestamp.Time,
			UpdateTime: time.Now(), // In real implementation, derive from status
		}

		summaries = append(summaries, summary)
	}

	resp := message.ListHelmReleasesResp{
		HelmReleases: summaries,
		ContinueResponse: message.ContinueResponse{
			ContinueToken: hrList.Continue,
		},
	}

	return resp, nil
}

// UpdateHelmRelease updates an existing HelmRelease CR
func (s *HelmReleaseService) UpdateHelmRelease(ctx context.Context, req message.UpdateHelmReleaseReq) (message.UpdateHelmReleaseResp, error) {
	// First get the existing HelmRelease
	existing, err := s.helmReleaseRW.Get(ctx, constants.OpsSystemNamespace, req.Name)
	if err != nil {
		if errors.Is(err, errors.HelmReleaseNotFound) {
			return message.UpdateHelmReleaseResp{}, err
		}
		return message.UpdateHelmReleaseResp{}, fmt.Errorf("failed to get existing HelmRelease: %w", err)
	}

	// Convert API request directly to FluxCD HelmRelease CR
	updatedFluxHelmRelease, err := message.ConvertToFluxCDHelmRelease(req.Name, existing.Spec.TargetNamespace, req.Component, req.Config)
	if err != nil {
		return message.UpdateHelmReleaseResp{}, fmt.Errorf("failed to convert to FluxCD HelmRelease: %w", err)
	}

	updatedFluxHelmRelease.ResourceVersion = existing.ResourceVersion
	stampHelmReleaseReconcileRequest(updatedFluxHelmRelease)

	// Update the HelmRelease CR
	err = s.helmReleaseRW.Update(ctx, updatedFluxHelmRelease)
	if err != nil {
		return message.UpdateHelmReleaseResp{}, fmt.Errorf("failed to update HelmRelease: %w", err)
	}

	// Return updated response
	now := time.Now()
	resp := message.UpdateHelmReleaseResp{
		Name:       req.Name,
		Namespace:  existing.Spec.TargetNamespace,
		Component:  req.Component,
		Config:     req.Config,
		Status:     message.ConvertFluxCDStatusToSummary(updatedFluxHelmRelease.Status),
		UpdateTime: now,
	}

	return resp, nil
}

// DeleteHelmRelease deletes a HelmRelease CR
func (s *HelmReleaseService) DeleteHelmRelease(ctx context.Context, req message.DeleteHelmReleaseReq) (message.DeleteHelmReleaseResp, error) {
	// Delete the HelmRelease CR
	err := s.helmReleaseRW.Delete(ctx, constants.OpsSystemNamespace, req.Name)
	if err != nil {
		if errors.Is(err, errors.HelmReleaseNotFound) {
			return message.DeleteHelmReleaseResp{}, err
		}
		return message.DeleteHelmReleaseResp{}, fmt.Errorf("failed to delete HelmRelease: %w", err)
	}

	resp := message.DeleteHelmReleaseResp{
		Message: fmt.Sprintf("HelmRelease %s deleted successfully", req.Name),
	}

	return resp, nil
}

// UpdateBasicAuth updates basic auth credentials for alertmanager and kube-prometheus-stack HelmReleases
func (s *HelmReleaseService) UpdateBasicAuth(ctx context.Context, req message.UpdateBasicAuthReq) (message.UpdateBasicAuthResp, error) {
	// Update alertmanager HelmRelease - fail fast on error
	if err := s.updateAlertmanagerBasicAuth(ctx, req.Username, req.Password); err != nil {
		return message.UpdateBasicAuthResp{}, errors.Wrap(errors.BasicAuthUpdateError, "failed to update alertmanager basic auth", err)
	}

	// Update kube-prometheus-stack HelmRelease
	if err := s.updateKubePrometheusStackBasicAuth(ctx, req.Username, req.Password); err != nil {
		return message.UpdateBasicAuthResp{}, errors.Wrap(errors.BasicAuthUpdateError, "failed to update kube-prometheus-stack basic auth", err)
	}

	return message.UpdateBasicAuthResp{}, nil
}

// updateAlertmanagerBasicAuth updates the alertmanager HelmRelease with new basic auth credentials
func (s *HelmReleaseService) updateAlertmanagerBasicAuth(ctx context.Context, username, password string) error {
	// Get existing alertmanager HelmRelease
	existing, err := s.helmReleaseRW.Get(ctx, constants.OpsSystemNamespace, constants.ReleaseNameAlertManager)
	if err != nil {
		return errors.Wrap(errors.HelmReleaseNotFound, "alertmanager HelmRelease not found", err)
	}

	// Get current values
	var currentValues map[string]interface{}
	if existing.Spec.Values != nil && len(existing.Spec.Values.Raw) > 0 {
		if err := json.Unmarshal(existing.Spec.Values.Raw, &currentValues); err != nil {
			return errors.Wrap(errors.HelmReleaseConfigError, "failed to unmarshal alertmanager values", err)
		}
	} else {
		currentValues = make(map[string]interface{})
	}

	// Update basic auth credentials in extraSecret
	if currentValues["extraSecret"] == nil {
		currentValues["extraSecret"] = make(map[string]interface{})
	}
	extraSecret, ok := currentValues["extraSecret"].(map[string]interface{})
	if !ok {
		return basicAuthConfigErr
	}

	if extraSecret["data"] == nil {
		extraSecret["data"] = make(map[string]interface{})
	}
	data, ok := extraSecret["data"].(map[string]interface{})
	if !ok {
		return basicAuthConfigErr
	}

	data["username"] = username
	data["password"] = password

	values, err := message.ConvertValuesToJSON(currentValues)
	if err != nil {
		return errors.Wrap(errors.UnmarshalError, "failed to update auth", err)
	}
	updated := existing.DeepCopy()
	updated.Spec.Values = values
	stampHelmReleaseReconcileRequest(updated)

	// Update the HelmRelease CR
	if err := s.helmReleaseRW.Update(ctx, updated); err != nil {
		return errors.Wrap(errors.HelmReleaseUpdateError, "failed to update alertmanager HelmRelease", err)
	}

	return nil
}

// updateKubePrometheusStackBasicAuth updates the kube-prometheus-stack HelmRelease with new basic auth credentials
func (s *HelmReleaseService) updateKubePrometheusStackBasicAuth(ctx context.Context, username, password string) error {
	// Get existing kube-prometheus-stack HelmRelease
	existing, err := s.helmReleaseRW.Get(ctx, constants.OpsSystemNamespace, constants.ReleaseNameKubePrometheusStack)
	if err != nil {
		return errors.Wrap(errors.HelmReleaseNotFound, "kube-prometheus-stack HelmRelease not found", err)
	}

	// Get current values
	var currentValues map[string]interface{}
	if existing.Spec.Values != nil && len(existing.Spec.Values.Raw) > 0 {
		if err := json.Unmarshal(existing.Spec.Values.Raw, &currentValues); err != nil {
			return errors.Wrap(errors.HelmReleaseConfigError, "failed to unmarshal kube-prometheus-stack values", err)
		}
	} else {
		currentValues = make(map[string]interface{})
	}

	// Update basic auth credentials in alertmanager config
	if err := s.updateAlertmanagerConfigBasicAuth(currentValues, username, password); err != nil {
		return errors.Wrap(errors.HelmReleaseUpdateError, "failed to update alertmanager config", err)
	}

	vals, err := message.ConvertValuesToJSON(currentValues)
	if err != nil {
		return errors.Wrap(errors.UnmarshalError, "failed to update auth", err)
	}
	updated := existing.DeepCopy()
	updated.Spec.Values = vals
	stampHelmReleaseReconcileRequest(updated)

	// Update the HelmRelease CR
	if err := s.helmReleaseRW.Update(ctx, updated); err != nil {
		return errors.Wrap(errors.HelmReleaseUpdateError, "failed to update kube-prometheus-stack HelmRelease", err)
	}

	return nil
}

// updateAlertmanagerConfigBasicAuth updates basic auth in alertmanager config within kube-prometheus-stack values
func (s *HelmReleaseService) updateAlertmanagerConfigBasicAuth(values map[string]interface{}, username, password string) error {
	// Navigate to alertmanager.config.receivers
	alertmanager, ok := values["alertmanager"].(map[string]interface{})
	if !ok {
		return basicAuthConfigErr
	}

	config, ok := alertmanager["config"].(map[string]interface{})
	if !ok {
		return basicAuthConfigErr
	}

	receivers, ok := config["receivers"].([]interface{})
	if !ok {
		return basicAuthConfigErr
	}

	// Update basic auth for relevant receivers
	for _, receiver := range receivers {
		receiverMap, ok := receiver.(map[string]interface{})
		if !ok {
			continue
		}

		name, ok := receiverMap["name"].(string)
		if !ok {
			continue
		}

		// Update basic auth for pingcap receivers
		if strings.Contains(name, "pingcap") {
			webhookConfigs, ok := receiverMap["webhook_configs"].([]interface{})
			if !ok {
				return basicAuthConfigErr
			}

			for _, webhookConfig := range webhookConfigs {
				webhookConfigMap, ok := webhookConfig.(map[string]interface{})
				if !ok {
					continue
				}

				httpConfig, ok := webhookConfigMap["http_config"].(map[string]interface{})
				if !ok {
					return basicAuthConfigErr
				}

				basicAuth, ok := httpConfig["basic_auth"].(map[string]interface{})
				if !ok {
					return basicAuthConfigErr
				}

				basicAuth["username"] = username
				basicAuth["password"] = password
			}
		}
	}

	return nil
}
