package cmd

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"text/tabwriter"
	"time"

	"github.com/spf13/cobra"

	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

var skillOpts struct {
	format string
}

var skillImportOpts struct {
	id          string
	name        string
	description string
	version     string
	author      string
	tags        string
	force       bool
}

type skillInfo struct {
	ID          string     `json:"id"                    yaml:"id"                    toml:"ID"`
	Name        string     `json:"name"                  yaml:"name"                  toml:"Name"`
	Description string     `json:"description,omitempty" yaml:"description,omitempty" toml:"Description,omitempty"`
	Version     string     `json:"version,omitempty"     yaml:"version,omitempty"     toml:"Version,omitempty"`
	Author      string     `json:"author,omitempty"      yaml:"author,omitempty"      toml:"Author,omitempty"`
	Tags        []string   `json:"tags,omitempty"        yaml:"tags,omitempty"        toml:"Tags,omitempty"`
	FileName    string     `json:"file_name"             yaml:"file_name"             toml:"FileName"`
	CreatedAt   *time.Time `json:"created_at,omitempty"  yaml:"created_at,omitempty"  toml:"CreatedAt,omitempty"`
}

func skillToInfo(s profile.Skill) skillInfo {
	return skillInfo{
		ID:          s.ID,
		Name:        s.Name,
		Description: s.Description,
		Version:     s.Version,
		Author:      s.Author,
		Tags:        s.Tags,
		FileName:    s.FileName,
		CreatedAt:   s.CreatedAt,
	}
}

var skillCmd = &cobra.Command{
	Use:   "skill",
	Short: "Manage skills",
	Long: `Manage reusable skill documents for agents.

Skills are markdown files that provide domain-specific instructions and
workflows. They can be imported from .md files or archives (.zip, .tar.gz, .tgz)
containing a SKILL.md file.

Skill files may include YAML front-matter with metadata (id, name, description,
version, author, tags). If front-matter is missing or incomplete, AgentTeam infers
metadata from the Markdown heading and source filename; CLI flags can override
the inferred values.

Examples:
  agentteam skill list
  agentteam skill import ./code-review.md
  agentteam skill import ./my-skill.zip
  agentteam skill import ./my-skill.tar.gz --id code-review --name "Code Review"
  agentteam skill get code-review
  agentteam skill delete code-review`,
}

var skillListCmd = &cobra.Command{
	Use:   "list",
	Short: "List all skills",
	Long: `List all imported skills.

Use --format/-f to control the output format.

Examples:
  agentteam skill list
  agentteam skill list -f json
  agentteam skill list --format yaml`,
	RunE: runSkillList,
}

var skillGetCmd = &cobra.Command{
	Use:   "get <id>",
	Short: "Show skill details",
	Long: `Show detailed information about a specific skill.

Examples:
  agentteam skill get code-review
  agentteam skill get code-review -f json`,
	Args: cobra.ExactArgs(1),
	RunE: runSkillGet,
}

var skillImportCmd = &cobra.Command{
	Use:   "import <file>",
	Short: "Import a skill from a file or archive",
	Long: `Import a skill from a file, archive, or URL.

Supported sources:
  - Local files:   .md, .markdown
  - Archives:      .zip, .tar, .tar.gz, .tgz
  - Remote URL:    http:// or https:// (auto-downloaded to temp file)
  - Repo URL:      GitHub/GitLab repository URLs (auto-resolved to archives)

For markdown files, the skill content is the file itself.
For archives, agentteam automatically discovers one or more directories containing
SKILL.md and imports each discovered skill while preserving relative paths.

Skill files may include YAML front-matter with metadata:

  ---
  id: code-review
  name: Code Review
  description: Automated code review guidelines
  version: "1.0"
  author: team
  tags: [review, quality]
  ---

  # Code Review Skill
  ...

If the file lacks front-matter or required fields (id, name), AgentTeam infers them
from the Markdown heading and source filename. Use --id and --name only when you
want to override the inferred values.

If a skill with the same ID already exists, you will be prompted to confirm
overwriting. Use --force/-f to skip the prompt and overwrite automatically.

Examples:
  agentteam skill import ./code-review.md
  agentteam skill import ./skills.zip
  agentteam skill import ./review.md --id code-review --name "Code Review"
  agentteam skill import ./bundle.tar.gz --id my-skill --name "My Skill" --description "A custom skill"
  agentteam skill import ./code-review.md --force
  agentteam skill import https://example.com/skills/code-review.md
  agentteam skill import https://github.com/openai/superpowers`,
	Args: cobra.ExactArgs(1),
	RunE: runSkillImport,
}

var skillDeleteCmd = &cobra.Command{
	Use:     "delete <id>",
	Aliases: []string{"rm", "remove"},
	Short:   "Delete a skill",
	Long: `Delete a skill and its content file from disk.

Examples:
  agentteam skill delete code-review
  agentteam skill rm code-review`,
	Args: cobra.ExactArgs(1),
	RunE: runSkillDelete,
}

func init() {
	skillListCmd.Flags().StringVarP(&skillOpts.format, "format", "f", "table", "output format (table|json|jsonc|yaml|toml)")
	skillGetCmd.Flags().StringVarP(&skillOpts.format, "format", "f", "table", "output format (table|json|jsonc|yaml|toml)")

	skillImportCmd.Flags().StringVar(&skillImportOpts.id, "id", "", "skill ID (required if not in front-matter)")
	skillImportCmd.Flags().StringVar(&skillImportOpts.name, "name", "", "skill name (required if not in front-matter)")
	skillImportCmd.Flags().StringVar(&skillImportOpts.description, "description", "", "skill description")
	skillImportCmd.Flags().StringVar(&skillImportOpts.version, "version", "", "skill version")
	skillImportCmd.Flags().StringVar(&skillImportOpts.author, "author", "", "skill author")
	skillImportCmd.Flags().StringVar(&skillImportOpts.tags, "tags", "", "comma-separated tags")
	skillImportCmd.Flags().BoolVarP(&skillImportOpts.force, "force", "f", false, "overwrite existing skill without confirmation")

	skillCmd.AddCommand(skillListCmd)
	skillCmd.AddCommand(skillGetCmd)
	skillCmd.AddCommand(skillImportCmd)
	skillCmd.AddCommand(skillDeleteCmd)
}

func buildImportMeta() *profile.SkillMeta {
	meta := &profile.SkillMeta{
		ID:          skillImportOpts.id,
		Name:        skillImportOpts.name,
		Description: skillImportOpts.description,
		Version:     skillImportOpts.version,
		Author:      skillImportOpts.author,
	}
	if skillImportOpts.tags != "" {
		for _, t := range strings.Split(skillImportOpts.tags, ",") {
			t = strings.TrimSpace(t)
			if t != "" {
				meta.Tags = append(meta.Tags, t)
			}
		}
	}
	return meta
}

func runSkillList(_ *cobra.Command, _ []string) error {
	op, err := GetOperator()
	if err != nil {
		return err
	}
	skills, err := op.Skills().All()
	if err != nil {
		return err
	}

	switch strings.ToLower(skillOpts.format) {
	case "table":
		return printSkillListTable(skills)
	case "json":
		return printSkillListJSON(skills, false)
	case "jsonc":
		return printSkillListJSON(skills, true)
	case "yaml":
		return printSkillListYAML(skills)
	case "toml":
		return printSkillListTOML(skills)
	default:
		return fmt.Errorf("unsupported format: %s (valid: table, json, jsonc, yaml, toml)", skillOpts.format)
	}
}

func runSkillGet(_ *cobra.Command, args []string) error {
	op, err := GetOperator()
	if err != nil {
		return err
	}
	s, ok, err := op.Skills().Get(args[0])
	if err != nil {
		return err
	}
	if !ok {
		return fmt.Errorf("skill %q not found", args[0])
	}

	info := skillToInfo(s)

	switch strings.ToLower(skillOpts.format) {
	case "table":
		return printSkillDetailTable(info)
	case "json":
		return printJSON(info, false)
	case "jsonc":
		return printJSON(info, true)
	case "yaml":
		return printYAML(info)
	case "toml":
		return printTOML(info)
	default:
		return fmt.Errorf("unsupported format: %s (valid: table, json, jsonc, yaml, toml)", skillOpts.format)
	}
}

func runSkillImport(_ *cobra.Command, args []string) error {
	op, err := GetOperator()
	if err != nil {
		return err
	}
	source := args[0]
	meta := buildImportMeta()

	isURL := strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://")

	doImport := func(force bool) (*profile.AutoImportResult, error) {
		if isURL {
			return op.Skills().ImportAutoFromURL(source, meta, force)
		}
		if _, err := os.Stat(source); os.IsNotExist(err) {
			return nil, fmt.Errorf("file %q not found; please check the path and try again", source)
		}
		return op.Skills().ImportAutoFromFile(source, meta, force)
	}

	force := skillImportOpts.force
	if !force {
		result, err := doImport(false)
		if err == nil {
			if result.NeedsMeta {
				return fmt.Errorf(
					"the file does not contain required metadata (id, name).\n\n"+
						"Please provide them via CLI flags:\n"+
						"  agentteam skill import %s --id <skill-id> --name <skill-name>\n\n"+
						"Or add YAML front-matter to the beginning of the file:\n"+
						"  ---\n"+
						"  id: my-skill\n"+
						"  name: My Skill\n"+
						"  description: A brief description\n"+
						"  ---",
					source,
				)
			}
			return printSkillImportResult(result)
		}

		if !errors.Is(err, profile.ErrSkillExists) {
			return err
		}

		if !promptYesNo(fmt.Sprintf("Skill %q already exists. Overwrite?", source), false) {
			fmt.Println("Import cancelled.")
			return nil
		}
		force = true
	}

	result, err := doImport(force)
	if err != nil {
		return err
	}

	if result.NeedsMeta {
		return fmt.Errorf(
			"the file does not contain required metadata (id, name).\n\n"+
				"Please provide them via CLI flags:\n"+
				"  agentteam skill import %s --id <skill-id> --name <skill-name>\n\n"+
				"Or add YAML front-matter to the beginning of the file:\n"+
				"  ---\n"+
				"  id: my-skill\n"+
				"  name: My Skill\n"+
				"  description: A brief description\n"+
				"  ---",
			source,
		)
	}

	return printSkillImportResult(result)
}

func printSkillImportResult(result *profile.AutoImportResult) error {
	if result == nil {
		return fmt.Errorf("skill import returned no result")
	}

	if result.Skill != nil {
		fmt.Printf("Skill %q (%s) imported successfully.\n", result.Skill.ID, result.Skill.Name)
		return nil
	}

	if result.Batch == nil {
		return fmt.Errorf("skill import returned no skill data")
	}

	for _, item := range result.Batch.Imported {
		fmt.Printf("Skill %q (%s): %s.\n", item.ID, item.Name, item.Status)
	}
	fmt.Printf("Batch import finished: %d created, %d skipped.\n", result.Batch.Created, result.Batch.Skipped)

	if len(result.Batch.Errors) > 0 {
		return fmt.Errorf("batch import completed with errors:\n- %s", strings.Join(result.Batch.Errors, "\n- "))
	}

	return nil
}

func runSkillDelete(_ *cobra.Command, args []string) error {
	op, err := GetOperator()
	if err != nil {
		return err
	}
	if err := op.Skills().Delete(args[0]); err != nil {
		return err
	}
	fmt.Printf("Skill %q deleted successfully.\n", args[0])
	return nil
}

// ---------------------------------------------------------------------------
// Output helpers
// ---------------------------------------------------------------------------

func printSkillListTable(skills []profile.Skill) error {
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	fmt.Fprintln(w, "ID\tNAME\tVERSION\tAUTHOR")
	for _, s := range skills {
		fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
			s.ID, s.Name, s.Version, s.Author)
	}
	return w.Flush()
}

func printSkillDetailTable(info skillInfo) error {
	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
	fmt.Fprintf(w, "ID\t%s\n", info.ID)
	fmt.Fprintf(w, "Name\t%s\n", info.Name)
	if info.Description != "" {
		fmt.Fprintf(w, "Description\t%s\n", info.Description)
	}
	if info.Version != "" {
		fmt.Fprintf(w, "Version\t%s\n", info.Version)
	}
	if info.Author != "" {
		fmt.Fprintf(w, "Author\t%s\n", info.Author)
	}
	if len(info.Tags) > 0 {
		fmt.Fprintf(w, "Tags\t%s\n", strings.Join(info.Tags, ", "))
	}
	fmt.Fprintf(w, "File\t%s\n", info.FileName)
	if info.CreatedAt != nil {
		fmt.Fprintf(w, "Created\t%s\n", info.CreatedAt.Format("2006-01-02T15:04:05Z07:00"))
	}
	return w.Flush()
}

func printSkillListJSON(skills []profile.Skill, pretty bool) error {
	infos := make([]skillInfo, len(skills))
	for i, s := range skills {
		infos[i] = skillToInfo(s)
	}
	return printJSON(infos, pretty)
}

func printSkillListYAML(skills []profile.Skill) error {
	infos := make([]skillInfo, len(skills))
	for i, s := range skills {
		infos[i] = skillToInfo(s)
	}
	return printYAML(infos)
}

func printSkillListTOML(skills []profile.Skill) error {
	infos := make([]skillInfo, len(skills))
	for i, s := range skills {
		infos[i] = skillToInfo(s)
	}
	return printTOML(infos)
}
