package geminiweb

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"
)

type GeminiSession struct {
	Name         string `json:"name"`
	CookieFile   string `json:"cookie_file"`
	LastUsed     int64  `json:"last_used"`
	ActiveCount  int    `json:"active_count"`
	RateLimitEnd int64  `json:"rate_limit_end"`
}

type SessionPool struct {
	mu       sync.Mutex
	sessions []*GeminiSession
	dir      string
	maxCount int
}

func NewSessionPool(dir string, maxSessions int) (*SessionPool, error) {
	if err := os.MkdirAll(dir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create session dir: %w", err)
	}

	sessions, err := loadSessions(dir)
	if err != nil {
		return nil, fmt.Errorf("failed to load sessions: %w", err)
	}

	return &SessionPool{
		sessions: sessions,
		dir:      dir,
		maxCount: maxSessions,
	}, nil
}

func loadSessions(dir string) ([]*GeminiSession, error) {
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	var sessions []*GeminiSession
	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}
		name := entry.Name()
		metaPath := filepath.Join(dir, name, "session.json")
		data, err := os.ReadFile(metaPath)
		if err != nil {
			continue
		}

		var s GeminiSession
		if err := json.Unmarshal(data, &s); err != nil {
			continue
		}
		sessions = append(sessions, &s)
	}

	return sessions, nil
}

func (p *SessionPool) Count() int {
	p.mu.Lock()
	defer p.mu.Unlock()
	return len(p.sessions)
}

func (p *SessionPool) GetAvailable() *GeminiSession {
	p.mu.Lock()
	defer p.mu.Unlock()

	now := time.Now().UnixMilli()

	var available []*GeminiSession
	for _, s := range p.sessions {
		if s.RateLimitEnd < now {
			available = append(available, s)
		}
	}

	if len(available) == 0 {
		return nil
	}

	var best *GeminiSession
	for _, s := range available {
		if best == nil || s.ActiveCount < best.ActiveCount {
			best = s
		} else if s.ActiveCount == best.ActiveCount && s.LastUsed < best.LastUsed {
			best = s
		}
	}

	return best
}

func (p *SessionPool) StartSession(s *GeminiSession) {
	p.mu.Lock()
	defer p.mu.Unlock()
	s.ActiveCount++
	s.LastUsed = time.Now().UnixMilli()
	p.saveSession(s)
}

func (p *SessionPool) EndSession(s *GeminiSession) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if s.ActiveCount > 0 {
		s.ActiveCount--
	}
	p.saveSession(s)
}

func (p *SessionPool) RateLimitSession(s *GeminiSession, durationMs int64) {
	p.mu.Lock()
	defer p.mu.Unlock()
	s.RateLimitEnd = time.Now().UnixMilli() + durationMs
	p.saveSession(s)
}

func (p *SessionPool) saveSession(s *GeminiSession) {
	metaPath := filepath.Join(p.dir, s.Name, "session.json")
	data, err := json.MarshalIndent(s, "", "  ")
	if err != nil {
		return
	}
	_ = os.WriteFile(metaPath, data, 0644)
}

func (p *SessionPool) CreateSession(name string) (*GeminiSession, error) {
	p.mu.Lock()
	defer p.mu.Unlock()

	sessionDir := filepath.Join(p.dir, name)
	if err := os.MkdirAll(sessionDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create session dir: %w", err)
	}

	s := &GeminiSession{
		Name:       name,
		CookieFile: filepath.Join(sessionDir, "cookies.json"),
		LastUsed:   time.Now().UnixMilli(),
	}

	p.sessions = append(p.sessions, s)
	p.saveSession(s)

	return s, nil
}

func (p *SessionPool) GetSessionNames() []string {
	p.mu.Lock()
	defer p.mu.Unlock()
	names := make([]string, len(p.sessions))
	for i, s := range p.sessions {
		names[i] = s.Name
	}
	return names
}
