package miner

import (
	"encoding/hex"
	"log"
	"math/big"
	"sync"
	"sync/atomic"
	"time"

	"crypto-miner-agent/config"
	"crypto-miner-agent/job"
	"crypto-miner-agent/stats"
)

type ShareHandler func(jobID, nonce, hash string)

type Pool struct {
	threads  int
	cfg      config.RuntimeConfig
	reporter *stats.Reporter
	engines  []*Engine
	handler  ShareHandler
	schedule *ScheduleGuard

	mu         sync.RWMutex
	currentJob *job.Job
	stopCh     chan struct{}
	wg         sync.WaitGroup
	paused     atomic.Bool
	remotePause atomic.Bool

	hashesTotal atomic.Uint64
	sharesFound atomic.Uint64
}

func NewPool(threads int, cfg config.RuntimeConfig, reporter *stats.Reporter, handler ShareHandler) *Pool {
	if threads <= 0 {
		threads = 1
	}
	engines := make([]*Engine, threads)
	for i := range engines {
		engines[i] = NewEngine()
	}
	return &Pool{
		threads:  threads,
		cfg:      cfg,
		reporter: reporter,
		engines:  engines,
		handler:  handler,
		schedule: NewScheduleGuard(cfg, reporter),
		stopCh:   make(chan struct{}),
	}
}

func (p *Pool) SetJob(job *job.Job) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.currentJob = job
	if job == nil {
		return
	}
	seed := job.SeedHash
	if seed == "" && len(job.Blob) >= 64 {
		seed = job.Blob[:64]
	}
	for _, engine := range p.engines {
		if err := engine.SetJob(seed, job.Blob); err != nil {
			log.Printf("[miner] failed to set job: %v", err)
		}
	}
}

func (p *Pool) Start() {
	for i := 0; i < p.threads; i++ {
		p.wg.Add(1)
		go p.worker(i, p.engines[i])
	}
	go p.resourceGuard()
}

func (p *Pool) Stop() {
	close(p.stopCh)
	p.wg.Wait()
}

func (p *Pool) HashesPerSecond() float64 {
	return float64(p.hashesTotal.Load())
}

func (p *Pool) ResetHashCounter() {
	p.hashesTotal.Store(0)
}

func (p *Pool) PauseRemote() {
	p.remotePause.Store(true)
}

func (p *Pool) ResumeRemote() {
	p.remotePause.Store(false)
}

func (p *Pool) IsRemotePaused() bool {
	return p.remotePause.Load()
}

func (p *Pool) resourceGuard() {
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()
	for {
		select {
		case <-p.stopCh:
			return
		case <-ticker.C:
			p.paused.Store(!p.miningAllowed())
		}
	}
}

func (p *Pool) miningAllowed() bool {
	if !p.resourcesOK() {
		return false
	}
	if p.schedule != nil && !p.schedule.Allowed() {
		return false
	}
	return true
}

func (p *Pool) resourcesOK() bool {
	cpuPct := p.reporter.SystemCPUPercent()
	if cpuPct <= 0 {
		cpuPct, _ = p.reporter.Usage()
	}
	if p.cfg.MaxCPUUsage > 0 && cpuPct > float64(p.cfg.MaxCPUUsage) {
		return false
	}
	freeMB := p.reporter.FreeMemoryMB()
	if freeMB > 0 && freeMB < uint64(p.cfg.MinFreeRAM) {
		return false
	}
	totalMB := p.reporter.TotalMemoryMB()
	if totalMB > 0 && p.cfg.MaxMemoryPct > 0 {
		usedPct := float64(totalMB-freeMB) / float64(totalMB) * 100
		if usedPct > float64(p.cfg.MaxMemoryPct) {
			return false
		}
	}
	return true
}

func (p *Pool) worker(id int, engine *Engine) {
	defer p.wg.Done()

	var nonce uint32 = uint32(id * 1000000)

	for {
		select {
		case <-p.stopCh:
			return
		default:
		}

		if p.paused.Load() || p.remotePause.Load() {
			time.Sleep(2 * time.Second)
			continue
		}

		p.mu.RLock()
		job := p.currentJob
		p.mu.RUnlock()
		if job == nil || job.Blob == "" {
			time.Sleep(500 * time.Millisecond)
			continue
		}

		for batch := 0; batch < 256; batch++ {
			select {
			case <-p.stopCh:
				return
			default:
			}
			if p.paused.Load() || p.remotePause.Load() {
				break
			}

			hashHex, _, err := engine.HashAtNonce(nonce)
			if err != nil {
				log.Printf("[miner] hash error: %v", err)
				break
			}
			p.hashesTotal.Add(1)
			nonce++

			target := job.Target
			if target == "" && job.Difficulty > 0 {
				target = difficultyToTargetHex(job.Difficulty)
			}
			if target != "" && hashMeetsTarget(hashHex, target) {
				p.sharesFound.Add(1)
				if p.handler != nil {
					p.handler(job.ID, uint32ToHex(nonce-1), hashHex)
				}
			}
		}
	}
}

func uint32ToHex(n uint32) string {
	b := []byte{byte(n), byte(n >> 8), byte(n >> 16), byte(n >> 24)}
	return hexEncode(b)
}

func hexEncode(b []byte) string {
	const hexdigits = "0123456789abcdef"
	out := make([]byte, len(b)*2)
	for i, v := range b {
		out[i*2] = hexdigits[v>>4]
		out[i*2+1] = hexdigits[v&0x0f]
	}
	return string(out)
}

func difficultyToTargetHex(difficulty int64) string {
	if difficulty <= 0 {
		return ""
	}
	maxTarget := new(big.Int)
	maxTarget.SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
	target := new(big.Int).Div(maxTarget, big.NewInt(difficulty))
	bytes := target.Bytes()
	padded := make([]byte, 32)
	copy(padded[32-len(bytes):], bytes)
	for i, j := 0, len(padded)-1; i < j; i, j = i+1, j-1 {
		padded[i], padded[j] = padded[j], padded[i]
	}
	return hex.EncodeToString(padded)
}
