package hub

import (
	"archive/zip"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"gopkg.in/yaml.v3"
)

type Team struct {
	Name        string   `yaml:"name"`
	Description string   `yaml:"description"`
	Author      string   `yaml:"author"`
	RepoURL     string   `yaml:"repo_url"`
	Agents      []string `yaml:"agents"`
	Skills      []string `yaml:"skills"`
	InstalledAt string   `yaml:"installed_at"`
	UpdatedAt   string   `yaml:"updated_at"`
}

// Install clones a git repo (any URL) and installs the team.
func Install(repoRef, agentsDir, skillsDir, teamsDir string) (*Team, error) {
	url := repoRef
	if !strings.HasPrefix(repoRef, "http") && !strings.HasPrefix(repoRef, "git@") {
		url = "https://github.com/" + repoRef
	}

	// Extract repo name for team name
	repoName := extractRepoName(url)

	tmp, err := os.MkdirTemp("", "agent-team-hub-*")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(tmp)

	cmd := exec.Command("git", "clone", "--depth=1", url, tmp)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("git clone: %w", err)
	}

	return installFromDir(tmp, repoName, url, agentsDir, skillsDir, teamsDir)
}

// InstallZIP extracts a ZIP file and installs the team.
func InstallZIP(zipPath, agentsDir, skillsDir, teamsDir string) (*Team, error) {
	// Extract team name from zip filename
	teamName := strings.TrimSuffix(filepath.Base(zipPath), ".zip")
	teamName = strings.TrimSuffix(teamName, "-main")
	teamName = strings.TrimSuffix(teamName, "-master")

	tmp, err := os.MkdirTemp("", "agent-team-hub-*")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(tmp)

	// Extract ZIP
	if err := ExtractZIP(zipPath, tmp); err != nil {
		return nil, fmt.Errorf("extract zip: %w", err)
	}

	// Find the root directory (might be wrapped in a subfolder)
	entries, _ := os.ReadDir(tmp)
	if len(entries) == 1 && entries[0].IsDir() {
		tmp = filepath.Join(tmp, entries[0].Name())
	}

	return installFromDir(tmp, teamName, "", agentsDir, skillsDir, teamsDir)
}

func installFromDir(tmp, teamName, repoURL, agentsDir, skillsDir, teamsDir string) (*Team, error) {
	// Copy agents
	installedAgents := []string{}
	srcAgentsDir := filepath.Join(tmp, "agents")
	if _, err := os.Stat(srcAgentsDir); err == nil {
		// Copy to team-specific directory
		dstAgentsDir := filepath.Join(agentsDir, teamName)
		if err := CopyDir(srcAgentsDir, dstAgentsDir); err == nil {
			entries, _ := os.ReadDir(srcAgentsDir)
			for _, e := range entries {
				if e.IsDir() {
					installedAgents = append(installedAgents, e.Name())
				}
			}
		}
	}

	// Copy skills
	installedSkills := []string{}
	srcSkillsDir := filepath.Join(tmp, "skills")
	if _, err := os.Stat(srcSkillsDir); err == nil {
		dstSkillsDir := filepath.Join(skillsDir, teamName)
		if err := CopyDir(srcSkillsDir, dstSkillsDir); err == nil {
			entries, _ := os.ReadDir(srcSkillsDir)
			for _, e := range entries {
				if e.IsDir() {
					installedSkills = append(installedSkills, e.Name())
				}
			}
		}
	}

	// Create knowledge dir
	os.MkdirAll(filepath.Join(agentsDir, teamName, "knowledge"), 0755)

	// Read TEAM.md for metadata
	teamMeta := readTeamMeta(tmp)

	// Create team record
	team := &Team{
		Name:        teamMeta.Name,
		Description: teamMeta.Description,
		Author:      teamMeta.Author,
		RepoURL:     repoURL,
		Agents:      installedAgents,
		Skills:      installedSkills,
		InstalledAt: time.Now().Format("2006-01-02"),
	}

	// Save team record
	teamDir := filepath.Join(teamsDir, teamName)
	os.MkdirAll(teamDir, 0755)
	team.Save(filepath.Join(teamDir, "team.yaml"))

	return team, nil
}

// Update 从 git 仓库拉取最新版本并更新团队文件，保留 memory/ 等用户数据。
func Update(teamName, agentsDir, skillsDir, teamsDir string) (*Team, error) {
	// 读取现有 team.yaml 获取 repo_url
	teamFile := filepath.Join(teamsDir, teamName, "team.yaml")
	existingData, err := os.ReadFile(teamFile)
	if err != nil {
		return nil, fmt.Errorf("团队 %s 不存在", teamName)
	}

	var existing Team
	// 简单解析（复用 server.go 中的解析逻辑风格）
	lines := strings.Split(string(existingData), "\n")
	for _, line := range lines {
		if strings.HasPrefix(line, "repo_url: ") {
			existing.RepoURL = strings.TrimSpace(strings.TrimPrefix(line, "repo_url: "))
		}
		if strings.HasPrefix(line, "installed_at: ") {
			existing.InstalledAt = strings.TrimSpace(strings.TrimPrefix(line, "installed_at: "))
		}
	}

	if existing.RepoURL == "" {
		return nil, fmt.Errorf("团队 %s 没有 repo_url，无法更新（可能是 ZIP 安装的）", teamName)
	}

	// Clone 最新版本到临时目录
	tmp, err := os.MkdirTemp("", "agent-team-update-*")
	if err != nil {
		return nil, err
	}
	defer os.RemoveAll(tmp)

	cmd := exec.Command("git", "clone", "--depth=1", existing.RepoURL, tmp)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("git clone: %w", err)
	}

	// 更新 agents：逐个 agent 目录，跳过 memory/
	srcAgentsDir := filepath.Join(tmp, "agents")
	dstAgentsDir := filepath.Join(agentsDir, teamName)
	updatedAgents := []string{}
	if _, err := os.Stat(srcAgentsDir); err == nil {
		entries, _ := os.ReadDir(srcAgentsDir)
		for _, e := range entries {
			if !e.IsDir() {
				continue
			}
			updatedAgents = append(updatedAgents, e.Name())
			srcAgent := filepath.Join(srcAgentsDir, e.Name())
			dstAgent := filepath.Join(dstAgentsDir, e.Name())
			if err := updateAgentDir(srcAgent, dstAgent); err != nil {
				return nil, fmt.Errorf("更新 agent %s 失败: %w", e.Name(), err)
			}
		}
	}

	// 更新 knowledge/（合并，不删除用户已有文件）
	srcKnowledge := filepath.Join(tmp, "knowledge")
	dstKnowledge := filepath.Join(agentsDir, teamName, "knowledge")
	if _, err := os.Stat(srcKnowledge); err == nil {
		CopyDir(srcKnowledge, dstKnowledge)
	}

	// 更新 skills
	updatedSkills := []string{}
	srcSkillsDir := filepath.Join(tmp, "skills")
	if _, err := os.Stat(srcSkillsDir); err == nil {
		entries, _ := os.ReadDir(srcSkillsDir)
		for _, e := range entries {
			if e.IsDir() {
				updatedSkills = append(updatedSkills, e.Name())
			}
		}
		CopyDir(srcSkillsDir, filepath.Join(skillsDir, teamName))
	}

	// 读取新的 TEAM.md 元数据
	teamMeta := readTeamMeta(tmp)

	// 更新 team.yaml，保留 installed_at
	team := &Team{
		Name:        teamMeta.Name,
		Description: teamMeta.Description,
		Author:      teamMeta.Author,
		RepoURL:     existing.RepoURL,
		Agents:      updatedAgents,
		Skills:      updatedSkills,
		InstalledAt: existing.InstalledAt,
		UpdatedAt:   time.Now().Format("2006-01-02 15:04:05"),
	}
	team.Save(teamFile)

	return team, nil
}

// updateAgentDir 更新单个 agent 目录：覆盖代码文件，保留 memory/
func updateAgentDir(src, dst string) error {
	os.MkdirAll(dst, 0755)
	entries, err := os.ReadDir(src)
	if err != nil {
		return err
	}
	for _, e := range entries {
		srcPath := filepath.Join(src, e.Name())
		dstPath := filepath.Join(dst, e.Name())
		if e.IsDir() {
			// 跳过 memory 目录，保留用户的记忆数据
			if e.Name() == "memory" {
				continue
			}
			if err := CopyDir(srcPath, dstPath); err != nil {
				return err
			}
		} else {
			data, err := os.ReadFile(srcPath)
			if err != nil {
				return err
			}
			if err := os.WriteFile(dstPath, data, 0644); err != nil {
				return err
			}
		}
	}
	return nil
}

func ExtractZIP(zipPath, dest string) error {
	r, err := zip.OpenReader(zipPath)
	if err != nil {
		return err
	}
	defer r.Close()

	for _, f := range r.File {
		fpath := filepath.Join(dest, f.Name)

		if f.FileInfo().IsDir() {
			os.MkdirAll(fpath, 0755)
			continue
		}

		if err := os.MkdirAll(filepath.Dir(fpath), 0755); err != nil {
			return err
		}

		outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
		if err != nil {
			return err
		}

		rc, err := f.Open()
		if err != nil {
			outFile.Close()
			return err
		}

		buf := make([]byte, 4096)
		for {
			n, err := rc.Read(buf)
			if n > 0 {
				if _, writeErr := outFile.Write(buf[:n]); writeErr != nil {
					rc.Close()
					outFile.Close()
					return writeErr
				}
			}
			if err != nil {
				break
			}
		}
		rc.Close()
		outFile.Close()
	}
	return nil
}

func extractRepoName(url string) string {
	parts := strings.Split(url, "/")
	name := parts[len(parts)-1]
	name = strings.TrimSuffix(name, ".git")
	return name
}

func (t *Team) Save(path string) error {
	content := fmt.Sprintf(`name: %s
description: %s
author: %s
repo_url: %s
agents:
%s
skills:
%s
installed_at: %s
updated_at: %s
`, t.Name, t.Description, t.Author, t.RepoURL, arrayToYAML(t.Agents), arrayToYAML(t.Skills), t.InstalledAt, t.UpdatedAt)
	return os.WriteFile(path, []byte(content), 0644)
}

func arrayToYAML(arr []string) string {
	if len(arr) == 0 {
		return "  []"
	}
	result := ""
	for _, s := range arr {
		result += fmt.Sprintf("  - %s\n", s)
	}
	return result
}

func CopyDir(src, dst string) error {
	if _, err := os.Stat(src); os.IsNotExist(err) {
		return nil // optional dir, skip
	}
	os.MkdirAll(dst, 0755)
	entries, err := os.ReadDir(src)
	if err != nil {
		return err
	}
	for _, e := range entries {
		srcPath := filepath.Join(src, e.Name())
		dstPath := filepath.Join(dst, e.Name())
		if e.IsDir() {
			if err := CopyDir(srcPath, dstPath); err != nil {
				return err
			}
		} else {
			data, err := os.ReadFile(srcPath)
			if err != nil {
				return err
			}
			os.MkdirAll(filepath.Dir(dstPath), 0755)
			if err := os.WriteFile(dstPath, data, 0644); err != nil {
				return err
			}
		}
	}
	return nil
}

func readTeamMeta(tmp string) Team {
	team := Team{}
	teamFile := filepath.Join(tmp, "TEAM.md")
	data, err := os.ReadFile(teamFile)
	if err != nil {
		return Team{Name: extractRepoName(tmp)}
	}

	content := string(data)
	if strings.HasPrefix(content, "---") {
		parts := strings.SplitN(content, "---", 3)
		if len(parts) >= 3 {
			yamlContent := parts[1]
			yaml.Unmarshal([]byte(yamlContent), &team)
		}
	}

	if team.Name == "" {
		team.Name = extractRepoName(tmp)
	}
	if team.Description == "" {
		team.Description = "团队描述"
	}

	return team
}
