package service

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/1Panel-dev/1Panel/agent/app/dto"
	"github.com/1Panel-dev/1Panel/agent/constant"
	"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)

const (
	openclawCronJobsRelativePath     = "data/conf/cron/jobs.json"
	openclawSessionStoreRelativeGlob = "data/conf/agents/*/sessions/sessions.json"
	openclawSessionFilesRelativeGlob = "data/conf/agents/*/sessions/*.jsonl"
)

func (a AgentService) GetOverview(req dto.AgentOverviewReq) (*dto.AgentOverview, error) {
	agent, install, conf, err := a.loadOpenclawAgentConfig(req.AgentID)
	if err != nil {
		return nil, err
	}

	overview := &dto.AgentOverview{
		Snapshot: dto.AgentOverviewSnapshot{
			ContainerStatus: install.Status,
			AppVersion:      install.Version,
			DefaultModel:    extractOpenclawDefaultModel(conf),
			ChannelCount:    countOpenclawConfiguredChannels(conf),
		},
	}
	if overview.Snapshot.DefaultModel == "" {
		overview.Snapshot.DefaultModel = agent.Model
	}
	if install.Status != constant.StatusRunning {
		return overview, nil
	}

	var wg sync.WaitGroup
	wg.Add(3)
	go func() {
		defer wg.Done()
		skillCount, err := loadOpenclawOverviewSkillStats(install.ContainerName)
		if err == nil {
			overview.Snapshot.SkillCount = skillCount
		}
	}()
	go func() {
		defer wg.Done()
		sessionCount, err := loadOpenclawOverviewSessionCount(install.GetPath())
		if err == nil {
			overview.Snapshot.SessionCount = sessionCount
		}
	}()
	go func() {
		defer wg.Done()
		jobCount, err := loadOpenclawOverviewJobCount(install.GetPath())
		if err == nil {
			overview.Snapshot.JobCount = jobCount
		}
	}()
	wg.Wait()

	return overview, nil
}

func extractOpenclawDefaultModel(conf map[string]interface{}) string {
	agents, ok := conf["agents"].(map[string]interface{})
	if !ok {
		return ""
	}
	defaults, ok := agents["defaults"].(map[string]interface{})
	if !ok {
		return ""
	}
	model, ok := defaults["model"].(map[string]interface{})
	if !ok {
		return ""
	}
	primary, _ := model["primary"].(string)
	return strings.TrimSpace(primary)
}

func countOpenclawConfiguredChannels(conf map[string]interface{}) int {
	channels, ok := conf["channels"].(map[string]interface{})
	if !ok {
		return 0
	}
	count := 0
	for _, value := range channels {
		channel, ok := value.(map[string]interface{})
		if !ok || len(channel) == 0 {
			continue
		}
		count++
	}
	return count
}

func loadOpenclawOverviewSkillStats(containerName string) (int, error) {
	output, err := cmd.RunDockerExecWithStdout(5*time.Minute, containerName, "openclaw", "skills", "list", "--json")
	if err != nil {
		return 0, err
	}
	if strings.TrimSpace(output) == "" {
		return 0, nil
	}
	skills, err := parseOpenclawSkillsList(output)
	if err != nil {
		return 0, err
	}
	return len(skills), nil
}

func loadOpenclawOverviewSessionCount(installPath string) (int, error) {
	sessionStorePaths, err := filepath.Glob(filepath.Join(installPath, openclawSessionStoreRelativeGlob))
	if err != nil {
		return 0, err
	}
	total := 0
	for _, sessionStorePath := range sessionStorePaths {
		count, err := loadOpenclawSessionCountFromFile(sessionStorePath)
		if err != nil {
			continue
		}
		total += count
	}
	if total > 0 {
		return total, nil
	}
	sessionFiles, err := filepath.Glob(filepath.Join(installPath, openclawSessionFilesRelativeGlob))
	if err != nil {
		return 0, err
	}
	return len(sessionFiles), nil
}

func loadOpenclawSessionCountFromFile(sessionStorePath string) (int, error) {
	content, err := os.ReadFile(sessionStorePath)
	if err != nil {
		return 0, err
	}
	return parseOpenclawSessionCount(string(content))
}

func loadOpenclawOverviewJobCount(installPath string) (int, error) {
	content, err := os.ReadFile(filepath.Join(installPath, openclawCronJobsRelativePath))
	if err != nil {
		if os.IsNotExist(err) {
			return 0, nil
		}
		return 0, err
	}
	return parseOpenclawCronCount(string(content))
}

func parseOpenclawSessionCount(output string) (int, error) {
	if strings.TrimSpace(output) == "" {
		return 0, nil
	}
	var payload interface{}
	if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &payload); err != nil {
		return 0, err
	}
	return countOpenclawSessions(payload), nil
}

func parseOpenclawCronCount(output string) (int, error) {
	if strings.TrimSpace(output) == "" {
		return 0, nil
	}
	var payload interface{}
	if err := json.Unmarshal([]byte(strings.TrimSpace(output)), &payload); err != nil {
		return 0, err
	}
	switch value := payload.(type) {
	case []interface{}:
		return len(value), nil
	case map[string]interface{}:
		if total, ok := value["total"].(float64); ok {
			return int(total), nil
		}
		if jobs, ok := value["jobs"].([]interface{}); ok {
			return len(jobs), nil
		}
		return len(value), nil
	default:
		return 0, nil
	}
}

func countOpenclawSessions(payload interface{}) int {
	switch value := payload.(type) {
	case []interface{}:
		return len(value)
	case map[string]interface{}:
		if count, ok := value["count"].(float64); ok {
			return int(count)
		}
		if sessions, ok := value["sessions"]; ok {
			return countOpenclawSessions(sessions)
		}
		count := 0
		for _, item := range value {
			switch typed := item.(type) {
			case map[string]interface{}:
				if looksLikeOpenclawSession(typed) {
					count++
				}
			case string:
				if strings.HasSuffix(strings.TrimSpace(typed), ".jsonl") {
					count++
				}
			}
		}
		return count
	default:
		return 0
	}
}

func looksLikeOpenclawSession(item map[string]interface{}) bool {
	if _, ok := item["sessionFile"]; ok {
		return true
	}
	if _, ok := item["agentId"]; ok {
		return true
	}
	if _, ok := item["lastActivityTs"]; ok {
		return true
	}
	if _, ok := item["updatedAt"]; ok {
		return true
	}
	return false
}
