package renewal

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"sync"
	"time"

	"github.com/bittorrent/go-btfs/chain"
	uh "github.com/bittorrent/go-btfs/core/commands/storage/upload/helper"
	"github.com/ipfs/go-datastore"
	"github.com/ipfs/go-datastore/query"
	logging "github.com/ipfs/go-log/v2"
)

const (
	userFileShard = "/btfs/%s/shards/file/%s"
)

var (
	autoRenewLog = logging.Logger("autorenew")
)

// AutoRenewalService manages automatic renewal of storage contracts
type AutoRenewalService struct {
	ctxParams     *uh.ContextParams
	ctx           context.Context
	cancel        context.CancelFunc
	ticker        *time.Ticker
	mu            sync.RWMutex
	running       bool
	checkInterval time.Duration
	stop          chan struct{}
}

// NewAutoRenewalService creates a new auto-renewal service
func NewAutoRenewalService(ctxParams *uh.ContextParams) *AutoRenewalService {
	ctx, cancel := context.WithCancel(ctxParams.Ctx)

	return &AutoRenewalService{
		ctxParams:     ctxParams,
		ctx:           ctx,
		cancel:        cancel,
		checkInterval: 24 * time.Hour, // Check every hour
		running:       false,
	}
}

// Start begins the auto-renewal service
func (ars *AutoRenewalService) Start() error {
	ars.mu.Lock()
	defer ars.mu.Unlock()

	if ars.running {
		return fmt.Errorf("auto-renewal service is already running")
	}

	ars.ticker = time.NewTicker(ars.checkInterval)
	ars.running = true

	go ars.run()

	autoRenewLog.Info("Auto-renewal service started")
	return nil
}

// Stop stops the auto-renewal service
func (ars *AutoRenewalService) Stop() error {
	ars.mu.Lock()
	defer ars.mu.Unlock()

	if !ars.running {
		return fmt.Errorf("auto-renewal service is not running")
	}

	ars.cancel()
	if ars.ticker != nil {
		ars.ticker.Stop()
	}
	ars.running = false

	ars.stop <- struct{}{}

	autoRenewLog.Info("Auto-renewal service stopped")
	return nil
}

// IsRunning returns whether the service is currently running
func (ars *AutoRenewalService) IsRunning() bool {
	ars.mu.RLock()
	defer ars.mu.RUnlock()
	return ars.running
}

// run is the main loop for the auto-renewal service
func (ars *AutoRenewalService) run() {
	defer func() {
		if r := recover(); r != nil {
			autoRenewLog.Errorf("Auto-renewal service panic: %v", r)
		}
	}()

	for {
		select {
		case <-ars.ticker.C:
			ars.checkAndRenewFiles()
		case <-ars.stop:
			autoRenewLog.Info("Auto-renewal service stopped")
			return
		}
	}
}

// checkAndRenewFiles checks for files that need renewal and processes them
func (ars *AutoRenewalService) checkAndRenewFiles() {
	autoRenewLog.Debug("Checking for files that need renewal")

	configs, err := ars.getAutoRenewalConfigs()
	if err != nil {
		autoRenewLog.Errorf("Failed to get auto-renewal configs: %v", err)
		return
	}

	now := time.Now()
	renewalThreshold := 24 * time.Hour // Renew 24 hours before expiration

	for _, config := range configs {
		if !config.Enabled {
			continue
		}

		// Check if renewal is needed (within threshold of expiration)
		if now.Add(renewalThreshold).After(config.NextRenewalAt) {
			autoRenewLog.Infof("Processing auto-renewal for file: %s", config.CID)

			err = ars.processAutoRenewal(config)
			if err != nil {
				autoRenewLog.Errorf("Failed to auto-renew file %s: %v", config.CID, err)
			} else {
				autoRenewLog.Infof("Successfully auto-renewed file: %s", config.CID)
			}
		}
	}
}

// getAutoRenewalConfigs retrieves all auto-renewal configurations
func (ars *AutoRenewalService) getAutoRenewalConfigs() ([]*RenewalInfo, error) {
	prefix := fmt.Sprintf(autoRenewKey, ars.ctxParams.N.Identity.String())
	q := query.Query{
		Prefix: prefix,
	}

	results, err := ars.ctxParams.N.Repo.Datastore().Query(ars.ctx, q)
	if err != nil {
		return nil, err
	}
	defer results.Close()

	var configs []*RenewalInfo

	for result := range results.Next() {
		if result.Error != nil {
			autoRenewLog.Errorf("Error reading auto-renewal config: %v", result.Error)
			continue
		}

		config := &RenewalInfo{}
		if err := json.Unmarshal(result.Value, config); err != nil {
			autoRenewLog.Errorf("Failed to unmarshal auto-renewal config: %v", err)
			continue
		}

		configs = append(configs, config)
	}

	return configs, nil
}

// processAutoRenewal processes the automatic renewal for a specific file
func (ars *AutoRenewalService) processAutoRenewal(config *RenewalInfo) error {

	shardInfos := make(map[string][]*RenewalShardInfo, 0)
	renewInfos := make(map[string]*RenewalInfo, 0)

	for _, s := range config.ShardsInfo {
		renewReq := &RenewRequest{
			CID:         config.CID,
			Token:       config.Token,
			Price:       uint64(config.Price),
			Duration:    config.RenewalDuration,
			SpId:        s.SPId,
			RenterID:    ars.ctxParams.N.Identity,
			ShardId:     s.ShardId,
			ContractId:  s.ContractID,
			ShardSize:   int64(s.ShardSize),
			OriginalEnd: config.NextRenewalAt,
			NewEnd:      config.NextRenewalAt.Add(time.Duration(config.RenewalDuration) * time.Hour * 24),
		}

		// Execute renewal
		resp, err := executeRenewal(ars.ctxParams, renewReq)
		if err != nil {
			return fmt.Errorf("renewal execution failed: %v", err)
		}

		renewShardInfo := &RenewalShardInfo{
			ContractID: s.ContractID,
			SPId:       s.SPId,
			ShardId:    s.ShardId,
			ShardSize:  s.ShardSize,
		}

		if shardInfos[config.CID] == nil {
			shardInfos[config.CID] = make([]*RenewalShardInfo, 0)
		}
		shardInfos[config.CID] = append(shardInfos[config.CID], renewShardInfo)

		renewInfos[config.CID] = &RenewalInfo{
			CID:             config.CID,
			ShardsInfo:      []*RenewalShardInfo{renewShardInfo},
			RenewalDuration: config.RenewalDuration,
			Token:           config.Token,
			Price:           config.Price,
			Enabled:         true,
			TotalPay:        resp.TotalCost,
			CreatedAt:       config.CreatedAt,
			LastRenewalAt:   config.LastRenewalAt,
			NextRenewalAt:   resp.NewExpiration,
		}
	}

	for cid, info := range shardInfos {
		newInfo := renewInfos[cid]
		newInfo.ShardsInfo = info
		_ = StoreRenewalInfo(ars.ctxParams, newInfo, RenewTypeAuto)

	}

	return nil
}

// updateAutoRenewalConfig updates an auto-renewal configuration
func (ars *AutoRenewalService) updateAutoRenewalConfig(config *RenewalInfo) error {
	configKey := fmt.Sprintf(autoRenewKey+"/%s", ars.ctxParams.N.Identity.String(), config.CID)

	configData, err := json.Marshal(config)
	if err != nil {
		return err
	}

	return ars.ctxParams.N.Repo.Datastore().Put(ars.ctx, datastore.NewKey(configKey), configData)
}

// GetAutoRenewalStatus returns the status of auto-renewal for a specific file
func (ars *AutoRenewalService) GetAutoRenewalStatus(fileHash string) (*RenewalInfo, error) {
	configKey := fmt.Sprintf(autoRenewKey+"/%s", ars.ctxParams.N.Identity.String(), fileHash)

	data, err := ars.ctxParams.N.Repo.Datastore().Get(ars.ctx, datastore.NewKey(configKey))
	if err != nil {
		if errors.Is(err, datastore.ErrNotFound) {
			return nil, nil
		}
		return nil, err
	}

	var config RenewalInfo
	err = json.Unmarshal(data, &config)
	if err != nil {
		return nil, err
	}

	return &config, nil
}

// DisableAutoRenewal disables auto-renewal for a specific file
func (ars *AutoRenewalService) DisableAutoRenewal(fileHash string) error {
	config, err := ars.GetAutoRenewalStatus(fileHash)
	if err != nil {
		return err
	}

	if config == nil {
		return fmt.Errorf("no auto-renewal config found for file: %s", fileHash)
	}

	err = chain.SettleObject.FileMetaService.UpdateAutoRenewal(fileHash, false)
	if err != nil {
		return err
	}

	config.Enabled = false
	return ars.updateAutoRenewalConfig(config)
}

// EnableAutoRenewal enables auto-renewal for a specific file
func (ars *AutoRenewalService) EnableAutoRenewal(fileHash string) error {
	config, err := ars.GetAutoRenewalStatus(fileHash)
	if err != nil {
		return err
	}

	if config == nil {
		return fmt.Errorf("no auto-renewal config found for file: %s", fileHash)
	}

	err = chain.SettleObject.FileMetaService.UpdateAutoRenewal(fileHash, true)
	if err != nil {
		return err
	}

	config.Enabled = true
	return ars.updateAutoRenewalConfig(config)
}

// Global auto-renewal service instance
var globalAutoRenewalService *AutoRenewalService
var autoRenewalServiceMu sync.Mutex

// GetGlobalAutoRenewalService returns the global auto-renewal service instance
func GetGlobalAutoRenewalService() *AutoRenewalService {
	autoRenewalServiceMu.Lock()
	defer autoRenewalServiceMu.Unlock()
	return globalAutoRenewalService
}

// SetGlobalAutoRenewalService sets the global auto-renewal service instance
func SetGlobalAutoRenewalService(service *AutoRenewalService) {
	autoRenewalServiceMu.Lock()
	defer autoRenewalServiceMu.Unlock()
	globalAutoRenewalService = service
}
