package healthcheck

import (
	"encoding/json"
	"fmt"
	"io"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/db/model"
	"github.com/pingcap-inc/clinic/pkg/o11y"
	"github.com/pingcap-inc/clinic/pkg/o11y/urls"
	"github.com/pingcap-inc/clinic/pkg/rbac"
)

const (
	healthCheckReportBucket = "delicated-healthcheck"
	healthCheckReportPrefix = "healthcheck-report"
)

type HealthCheckService struct {
	cfg          *config.Config
	rbac         rbac.Auth
	s3Client     s3GetObjectAPI
	urlsResolver *urls.ClusterURLsResolver
	repository   model.HealthCheckRepository
}

type s3GetObjectAPI interface {
	GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error)
}

func NewHealthCheckService(cfg *config.Config, rbac rbac.Auth) *HealthCheckService {
	s3Client := newHealthCheckS3Client(cfg)

	// Initialize URL resolver for cluster URLs
	var urlResolver *urls.ClusterURLsResolver
	if cfg != nil && cfg.CentralO11yConfig.GSEndpoint != "" {
		o11ySharedOpts := []o11y.Option{
			o11y.TimeoutOption(40 * time.Second),
			o11y.MaxIdleConnOption(10),
		}
		o11yCli, err := o11y.New(
			cfg.CentralO11yConfig.GSEndpoint,
			cfg.CentralO11yConfig.DSEndpointTPL,
			cfg.CentralO11yConfig.ClientId,
			cfg.CentralO11yConfig.ClientSecret,
			cfg.CentralO11yConfig.Env,
			append(o11ySharedOpts, o11y.ByocDSEndpointOption(cfg.CentralO11yConfig.ByocDSEndpoint))...,
		)
		if err != nil {
			log.Error("failed to create o11y client for healthcheck service", zap.Error(err))
		} else {
			urlResolver = urls.NewClusterURLsResolver(o11yCli)
			log.Info("url resolver initialized for healthcheck service")
		}
	}

	return &HealthCheckService{
		cfg:          cfg,
		rbac:         rbac,
		s3Client:     s3Client,
		urlsResolver: urlResolver,
		repository:   model.NewHealthCheckDB(),
	}
}

func newHealthCheckS3Client(cfg *config.Config) s3GetObjectAPI {
	if cfg != nil && cfg.FileStorage.AWSStorage.Region != "" {
		sess, err := session.NewSession(&aws.Config{
			Region: aws.String(cfg.FileStorage.AWSStorage.Region),
		})
		if err != nil {
			log.Error("failed to create s3 session for healthcheck service", zap.Error(err))
		} else {
			// Assume role for cross-account access
			assumeRoleARN := "arn:aws:iam::552185537300:role/clinic-job-role"
			stsClient := sts.New(sess)
			creds := stscreds.NewCredentialsWithClient(stsClient, assumeRoleARN)
			log.Info("s3 client initialized for healthcheck report reading",
				zap.String("region", cfg.FileStorage.AWSStorage.Region),
				zap.String("assume_role_arn", assumeRoleARN),
			)
			return s3.New(sess, &aws.Config{Credentials: creds})
		}
	}
	return nil
}

// NewHealthCheckServiceWithRepository creates a service with a custom repository (for testing)
func NewHealthCheckServiceWithRepository(cfg *config.Config, rbac rbac.Auth, repo model.HealthCheckRepository) *HealthCheckService {
	service := NewHealthCheckService(cfg, rbac)
	service.repository = repo
	return service
}

// getReportFromS3 reads the health check report from S3
func (s *HealthCheckService) getReportFromS3(clusterID, reportID, name string) (any, error) {
	return readHealthCheckReportFromS3(s.s3Client, clusterID, reportID, name)
}

func readHealthCheckReportFromS3(client s3GetObjectAPI, clusterID, reportID, name string) (any, error) {
	if client == nil {
		return nil, fmt.Errorf("s3 client not initialized")
	}

	// Build S3 key: healthcheck-report/{clusterId}_{reportId}_{name}.json
	s3Key := fmt.Sprintf("%s/%s_%s_%s.json", healthCheckReportPrefix, clusterID, reportID, name)

	log.Info("fetching health check report from s3",
		zap.String("bucket", healthCheckReportBucket),
		zap.String("key", s3Key),
	)

	resp, err := client.GetObject(&s3.GetObjectInput{
		Bucket: aws.String(healthCheckReportBucket),
		Key:    aws.String(s3Key),
	})
	if err != nil {
		if isHealthCheckS3NotFoundError(err) {
			log.Debug("health check report not ready in s3",
				zap.String("bucket", healthCheckReportBucket),
				zap.String("key", s3Key),
			)
			return nil, err
		}
		log.Error("failed to get object from s3",
			zap.Error(err),
			zap.String("bucket", healthCheckReportBucket),
			zap.String("key", s3Key),
		)
		return nil, err
	}
	defer resp.Body.Close()

	// Read body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Error("failed to read s3 object body", zap.Error(err))
		return nil, err
	}

	// Parse JSON to any
	var reportContent any
	if err := json.Unmarshal(body, &reportContent); err != nil {
		log.Error("failed to unmarshal report content", zap.Error(err))
		// Return raw string if not valid JSON
		return string(body), nil
	}

	return reportContent, nil
}
