package api

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"slices"
	"time"

	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/handler/awshandler"
	"github.com/pingcap/dbaas/billing2/utils/ginutils"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

type s3BillsDetailAPI struct{}

type PresignedURL string

func (u PresignedURL) MarshalJSON() ([]byte, error) {
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	enc.SetEscapeHTML(false)
	if err := enc.Encode(string(u)); err != nil {
		return nil, err
	}
	return bytes.TrimSpace(buf.Bytes()), nil
}

type S3BillsDetailPresignResponse struct {
	URL PresignedURL `json:"url"`
}

type S3BillsDetailMonthlyPresignResponse struct {
	BilledMonth string                           `json:"billedMonth"`
	Details     []S3BillsDetailDailyPresignEntry `json:"details"`
}

type S3BillsDetailDailyPresignEntry struct {
	Date string       `json:"date"`
	URL  PresignedURL `json:"url"`
}

type OpenapiDailyCostURI struct {
	BilledDate string `uri:"billedDate" binding:"required"`
}

func (s *s3BillsDetailAPI) GetBillsDetailPresignURLOpenAPI(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	query := &OpenapiMonthlyCostURI{}
	err := c.ShouldBindUri(query)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	billingCfgStruct, err := getBillingCfgFromAuth(c)
	if err != nil {
		return nil, err
	}
	log.Info("/billsDetailPresignUrl/{billedMonth} GetBillsDetailPresignURLOpenAPI",
		zap.Uint("tenantID", billingCfgStruct.TenantID),
		zap.String("billedMonth", query.BilledMonth))

	if !config.Get().GetBool(config.EnableS3BillsDetailExport) {
		return nil, billing_errors.NewInvalidInputError(errors.New("S3 bills detail export is not enabled"))
	}

	tenantKey := fmt.Sprintf("%d-%d", billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	tenantList := config.Get().GetStringSlice(config.S3BillsDetailTenantList)
	if !slices.Contains(tenantList, tenantKey) {
		return nil, billing_errors.NewInvalidInputError(errors.New("tenant is not enabled for S3 bills detail export"))
	}

	bucketName := config.Get().GetString(config.S3BillsDetailBucketName)
	prefix := awshandler.BuildS3BillsDetailMonthPrefix(billingCfgStruct.TenantID, query.BilledMonth)

	s3Cli, err := awshandler.NewS3ClientWithRegion(config.Get().GetString(config.S3BillsDetailRegion))
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}

	resp, err := s3Cli.ListObjectsV2(&s3.ListObjectsV2Input{
		Bucket: &bucketName,
		Prefix: &prefix,
	})
	if err != nil {
		log.Error("failed to list S3 objects",
			zap.Uint("tenantID", billingCfgStruct.TenantID),
			zap.String("prefix", prefix),
			zap.Error(err))
		return nil, billing_errors.NewSystemError(err)
	}

	result := S3BillsDetailMonthlyPresignResponse{
		BilledMonth: query.BilledMonth,
	}
	for _, obj := range resp.Contents {
		url, err := awshandler.GetBillsDetailPresignURL(*obj.Key)
		if err != nil {
			log.Error("failed to generate presign URL", zap.String("key", *obj.Key), zap.Error(err))
			continue
		}
		result.Details = append(result.Details, S3BillsDetailDailyPresignEntry{
			Date: extractDateFromKey(*obj.Key),
			URL:  PresignedURL(url),
		})
	}
	return result, nil
}

func (s *s3BillsDetailAPI) GetDailyBillsDetailPresignURLOpenAPI(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	query := &OpenapiDailyCostURI{}
	err := c.ShouldBindUri(query)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}
	billingCfgStruct, err := getBillingCfgFromAuth(c)
	if err != nil {
		return nil, err
	}
	log.Info("/billsDetails/daily/{billedDate}/presignUrl GetDailyBillsDetailPresignURLOpenAPI",
		zap.Uint("tenantID", billingCfgStruct.TenantID),
		zap.String("billedDate", query.BilledDate))

	if !config.Get().GetBool(config.EnableS3BillsDetailExport) {
		return nil, billing_errors.NewInvalidInputError(errors.New("S3 bills detail export is not enabled"))
	}

	tenantKey := fmt.Sprintf("%d-%d", billingCfgStruct.TenantID, billingCfgStruct.BillingConfigID)
	tenantList := config.Get().GetStringSlice(config.S3BillsDetailTenantList)
	if !slices.Contains(tenantList, tenantKey) {
		return nil, billing_errors.NewInvalidInputError(errors.New("tenant is not enabled for S3 bills detail export"))
	}

	_, err = time.Parse("2006-01-02", query.BilledDate)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(fmt.Errorf("invalid billedDate format, expected YYYY-MM-DD"))
	}

	if _, err := validateBillsDetailDate(query.BilledDate); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	key := awshandler.BuildS3BillsDetailKey(billingCfgStruct.TenantID, query.BilledDate)
	url, err := awshandler.GetBillsDetailPresignURL(key)
	if err != nil {
		log.Error("failed to generate presign URL",
			zap.Uint("tenantID", billingCfgStruct.TenantID),
			zap.String("billedDate", query.BilledDate),
			zap.Error(err))
		return nil, billing_errors.NewSystemError(err)
	}
	return S3BillsDetailPresignResponse{URL: PresignedURL(url)}, nil
}

func extractDateFromKey(key string) string {
	parts := splitKey(key)
	if len(parts) >= 1 {
		return parts[len(parts)-1]
	}
	return key
}

func splitKey(key string) []string {
	var result []string
	start := 0
	for i := 0; i < len(key); i++ {
		if key[i] == '/' {
			if i > start {
				result = append(result, key[start:i])
			}
			start = i + 1
		}
	}
	if start < len(key) {
		result = append(result, key[start:])
	}
	return result
}

func validateBillsDetailDate(dateStr string) (time.Time, error) {
	date, err := time.Parse("2006-01-02", dateStr)
	if err != nil {
		return time.Time{}, fmt.Errorf("invalid date format, expected YYYY-MM-DD: %w", err)
	}
	now := timeutils.GetNow()
	maxDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC).AddDate(0, 0, -1)
	minDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC).AddDate(0, -1, 0)
	if date.After(maxDate) {
		return time.Time{}, fmt.Errorf("date %s is after max allowed date %s", dateStr, maxDate.Format("2006-01-02"))
	}
	if date.Before(minDate) {
		return time.Time{}, fmt.Errorf("date %s is before min allowed date %s", dateStr, minDate.Format("2006-01-02"))
	}
	return date, nil
}

type S3BillsDetailTestURI struct {
	TenantID uint   `uri:"tenant_id" binding:"required"`
	Date     string `uri:"date" binding:"required"`
}

func (s *s3BillsDetailAPI) TestGetBillsDetailInnerAPI(c *gin.Context) (ginutils.HandlerFuncResult, error) {
	query := &S3BillsDetailTestURI{}
	if err := c.ShouldBindUri(query); err != nil {
		return nil, billing_errors.NewInvalidInputError(err)
	}

	_, err := time.Parse("2006-01-02", query.Date)
	if err != nil {
		return nil, billing_errors.NewInvalidInputError(fmt.Errorf("invalid date format, expected YYYY-MM-DD"))
	}

	key := awshandler.BuildS3BillsDetailKey(query.TenantID, query.Date)
	url, err := awshandler.GetBillsDetailPresignURL(key)
	if err != nil {
		log.Error("failed to generate presign URL",
			zap.Uint("tenantID", query.TenantID),
			zap.String("date", query.Date),
			zap.Error(err))
		return nil, billing_errors.NewSystemError(err)
	}
	return S3BillsDetailPresignResponse{URL: PresignedURL(url)}, nil
}

func InitializeInnerS3BillsDetailRouter(router ginutils.MyRoute) {
	s := &s3BillsDetailAPI{}
	router.HandleWithError("GET", "/s3BillsDetail/:tenant_id/:date", s.TestGetBillsDetailInnerAPI)
}
