package cli

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"io"
	"strings"

	"gemcms/internal/cms"
)

const usage = `gemcms is a CLI CMS for Gemini capsules.

Usage:
  gemcms add <file-or-directory> [--type article|page] [--section name] [--tags a,b] [--draft] [--menu name]
  gemcms create capsule <name> [--title title] [--host host] [--import path] [--section name] [--tags a,b] [--menu name] [--build]
  gemcms create site <name> [--title title] [--host host]
  gemcms create page <slug> [--title title]
  gemcms create article <title> [--section name] [--tags a,b] [--draft]
  gemcms create section <name> [--title title]
  gemcms create menu <name>
  gemcms create widget <type> [--name name] [--section name] [--limit n]
  gemcms menu add <menu> <home|page|section|article|custom> [target] [--label label] [--url url]
  gemcms list <pages|articles|menus|widgets>
  gemcms status
  gemcms health
  gemcms build
  gemcms check
`

func Run(ctx context.Context, args []string, out, errOut io.Writer) error {
	_ = ctx

	if len(args) == 0 {
		fmt.Fprint(out, usage)
		return nil
	}

	switch args[0] {
	case "help", "-h", "--help":
		fmt.Fprint(out, usage)
		return nil
	case "add":
		return runAdd(args[1:], out, errOut)
	case "create":
		return runCreate(args[1:], out, errOut)
	case "menu":
		return runMenu(args[1:], out, errOut)
	case "list":
		return runList(args[1:], out)
	case "status":
		return runStatus(args[1:], out, false)
	case "health":
		return runStatus(args[1:], out, true)
	case "build":
		root, err := cms.FindRoot(".")
		if err != nil {
			return err
		}
		report, err := cms.Build(root)
		if err != nil {
			return err
		}
		fmt.Fprintf(out, "built %d files into %s\n", report.FilesWritten, report.OutputDir)
		return nil
	case "check":
		root, err := cms.FindRoot(".")
		if err != nil {
			return err
		}
		report, err := cms.Check(root)
		if err != nil {
			return err
		}
		for _, issue := range report.Issues {
			fmt.Fprintf(out, "%s: %s: %s\n", issue.Level, issue.Path, issue.Message)
		}
		if report.HasErrors() {
			return errors.New("check failed")
		}
		if len(report.Issues) == 0 {
			fmt.Fprintln(out, "OK")
		}
		return nil
	default:
		return fmt.Errorf("unknown command %q\n\n%s", args[0], usage)
	}
}

func runAdd(args []string, out, errOut io.Writer) error {
	fs := newFlagSet("add", errOut)
	docType := fs.String("type", "", "content type: article or page")
	title := fs.String("title", "", "content title")
	slug := fs.String("slug", "", "content slug")
	section := fs.String("section", "articles", "article section")
	tags := fs.String("tags", "", "comma-separated tags")
	draft := fs.Bool("draft", false, "mark content as draft")
	menu := fs.String("menu", "", "menu to add the content to")
	positionals, err := parseInterspersed(fs, args)
	if err != nil {
		return err
	}
	if len(positionals) != 1 {
		return errors.New("usage: gemcms add <file-or-directory> [--type article|page] [--section name] [--tags a,b] [--draft] [--menu name]")
	}
	root, err := cms.FindRoot(".")
	if err != nil {
		return err
	}
	report, err := cms.AddPath(root, cms.AddOptions{
		Path:    positionals[0],
		Type:    *docType,
		Title:   *title,
		Slug:    *slug,
		Section: *section,
		Tags:    splitCSV(*tags),
		Draft:   *draft,
		Menu:    *menu,
	})
	if err != nil {
		return err
	}
	for _, doc := range report.Documents {
		fmt.Fprintf(out, "added %s %s\n", doc.Type, doc.SourcePath)
	}
	return nil
}

func runCreate(args []string, out, errOut io.Writer) error {
	if len(args) == 0 {
		return fmt.Errorf("missing create target\n\n%s", usage)
	}

	switch args[0] {
	case "site", "capsule":
		fs := newFlagSet("create site", errOut)
		title := fs.String("title", "", "capsule title")
		host := fs.String("host", "", "Gemini host")
		author := fs.String("author", "", "default author")
		language := fs.String("language", "en", "content language")
		importPath := fs.String("import", "", "file or directory to import after creating the capsule")
		importType := fs.String("type", "", "imported content type")
		section := fs.String("section", "articles", "section for imported articles")
		tags := fs.String("tags", "", "comma-separated tags for imported content")
		draft := fs.Bool("draft", false, "mark imported content as draft")
		menu := fs.String("menu", "", "menu to add imported content to")
		build := fs.Bool("build", false, "build public output after creation")
		positionals, err := parseInterspersed(fs, args[1:])
		if err != nil {
			return err
		}
		if len(positionals) != 1 {
			return errors.New("usage: gemcms create site <name> [--title title] [--host host]")
		}
		root, err := cms.CreateSite(".", cms.SiteOptions{
			Name:     positionals[0],
			Title:    *title,
			Host:     *host,
			Author:   *author,
			Language: *language,
		})
		if err != nil {
			return err
		}
		fmt.Fprintf(out, "created site %s\n", root)
		if *importPath != "" {
			if *section != "articles" {
				if _, err := cms.CreateSection(root, cms.SectionOptions{Name: *section}); err != nil {
					return err
				}
				if _, err := cms.UpsertWidget(root, cms.WidgetOptions{Name: "recent-articles", Type: "recent", Section: *section, Limit: 5}); err != nil {
					return err
				}
			}
			report, err := cms.AddPath(root, cms.AddOptions{
				Path:    *importPath,
				Type:    *importType,
				Section: *section,
				Tags:    splitCSV(*tags),
				Draft:   *draft,
				Menu:    *menu,
			})
			if err != nil {
				return err
			}
			for _, doc := range report.Documents {
				fmt.Fprintf(out, "added %s %s\n", doc.Type, doc.SourcePath)
			}
		}
		if *build {
			report, err := cms.Build(root)
			if err != nil {
				return err
			}
			fmt.Fprintf(out, "built %d files into %s\n", report.FilesWritten, report.OutputDir)
		}
		return nil
	case "page":
		fs := newFlagSet("create page", errOut)
		title := fs.String("title", "", "page title")
		menu := fs.String("menu", "", "menu to add the page to")
		positionals, err := parseInterspersed(fs, args[1:])
		if err != nil {
			return err
		}
		if len(positionals) != 1 {
			return errors.New("usage: gemcms create page <slug> [--title title]")
		}
		root, err := cms.FindRoot(".")
		if err != nil {
			return err
		}
		doc, err := cms.CreatePage(root, cms.PageOptions{
			Slug:  positionals[0],
			Title: *title,
			Menu:  *menu,
		})
		if err != nil {
			return err
		}
		fmt.Fprintf(out, "created page %s\n", doc.SourcePath)
		return nil
	case "article":
		fs := newFlagSet("create article", errOut)
		section := fs.String("section", "articles", "article section")
		tags := fs.String("tags", "", "comma-separated tags")
		draft := fs.Bool("draft", false, "mark article as draft")
		menu := fs.String("menu", "", "menu to add the article to")
		positionals, err := parseInterspersed(fs, args[1:])
		if err != nil {
			return err
		}
		if len(positionals) < 1 {
			return errors.New("usage: gemcms create article <title> [--section name] [--tags a,b] [--draft]")
		}
		root, err := cms.FindRoot(".")
		if err != nil {
			return err
		}
		doc, err := cms.CreateArticle(root, cms.ArticleOptions{
			Title:   strings.Join(positionals, " "),
			Section: *section,
			Tags:    splitCSV(*tags),
			Draft:   *draft,
			Menu:    *menu,
		})
		if err != nil {
			return err
		}
		fmt.Fprintf(out, "created article %s\n", doc.SourcePath)
		return nil
	case "section":
		fs := newFlagSet("create section", errOut)
		title := fs.String("title", "", "section title")
		positionals, err := parseInterspersed(fs, args[1:])
		if err != nil {
			return err
		}
		if len(positionals) != 1 {
			return errors.New("usage: gemcms create section <name> [--title title]")
		}
		root, err := cms.FindRoot(".")
		if err != nil {
			return err
		}
		path, err := cms.CreateSection(root, cms.SectionOptions{Name: positionals[0], Title: *title})
		if err != nil {
			return err
		}
		fmt.Fprintf(out, "created section %s\n", path)
		return nil
	case "menu":
		fs := newFlagSet("create menu", errOut)
		positionals, err := parseInterspersed(fs, args[1:])
		if err != nil {
			return err
		}
		if len(positionals) != 1 {
			return errors.New("usage: gemcms create menu <name>")
		}
		root, err := cms.FindRoot(".")
		if err != nil {
			return err
		}
		path, err := cms.CreateMenu(root, positionals[0])
		if err != nil {
			return err
		}
		fmt.Fprintf(out, "created menu %s\n", path)
		return nil
	case "widget":
		fs := newFlagSet("create widget", errOut)
		name := fs.String("name", "", "widget config name")
		section := fs.String("section", "articles", "section for recent widgets")
		limit := fs.Int("limit", 5, "item limit")
		positionals, err := parseInterspersed(fs, args[1:])
		if err != nil {
			return err
		}
		if len(positionals) != 1 {
			return errors.New("usage: gemcms create widget <type> [--name name] [--section name] [--limit n]")
		}
		root, err := cms.FindRoot(".")
		if err != nil {
			return err
		}
		path, err := cms.CreateWidget(root, cms.WidgetOptions{
			Type:    positionals[0],
			Name:    *name,
			Section: *section,
			Limit:   *limit,
		})
		if err != nil {
			return err
		}
		fmt.Fprintf(out, "created widget %s\n", path)
		return nil
	default:
		return fmt.Errorf("unknown create target %q", args[0])
	}
}

func runMenu(args []string, out, errOut io.Writer) error {
	if len(args) == 0 || args[0] != "add" {
		return errors.New("usage: gemcms menu add <menu> <home|page|section|article|custom> [target] [--label label] [--url url]")
	}
	if len(args) < 3 {
		return errors.New("usage: gemcms menu add <menu> <home|page|section|article|custom> [target] [--label label] [--url url]")
	}

	menuName := args[1]
	kind := args[2]
	target := ""
	flagStart := 3
	if requiresTarget(kind) {
		if len(args) < 4 || strings.HasPrefix(args[3], "-") {
			return fmt.Errorf("menu item %q requires a target", kind)
		}
		target = args[3]
		flagStart = 4
	}

	fs := newFlagSet("menu add", errOut)
	label := fs.String("label", "", "menu item label")
	url := fs.String("url", "", "menu item URL")
	positionals, err := parseInterspersed(fs, args[flagStart:])
	if err != nil {
		return err
	}
	if len(positionals) != 0 {
		return fmt.Errorf("unexpected argument %q", positionals[0])
	}

	root, err := cms.FindRoot(".")
	if err != nil {
		return err
	}
	if err := cms.AddMenuItem(root, cms.MenuItemOptions{
		Menu:   menuName,
		Kind:   kind,
		Target: target,
		Label:  *label,
		URL:    *url,
	}); err != nil {
		return err
	}
	fmt.Fprintf(out, "added item to menu %s\n", menuName)
	return nil
}

func runList(args []string, out io.Writer) error {
	if len(args) != 1 {
		return errors.New("usage: gemcms list <pages|articles|menus|widgets>")
	}
	root, err := cms.FindRoot(".")
	if err != nil {
		return err
	}
	items, err := cms.List(root, args[0])
	if err != nil {
		return err
	}
	for _, item := range items {
		fmt.Fprintln(out, item)
	}
	return nil
}

func runStatus(args []string, out io.Writer, healthOnly bool) error {
	if len(args) != 0 {
		if healthOnly {
			return errors.New("usage: gemcms health")
		}
		return errors.New("usage: gemcms status")
	}
	root, err := cms.FindRoot(".")
	if err != nil {
		return err
	}
	report, err := cms.Status(root)
	if err != nil {
		return err
	}
	if healthOnly {
		fmt.Fprintf(out, "%s (%d errors, %d warnings)\n", report.Health(), report.ErrorCount(), report.WarningCount())
		for _, issue := range report.Issues {
			fmt.Fprintf(out, "%s: %s: %s\n", issue.Level, issue.Path, issue.Message)
		}
		if report.ErrorCount() > 0 {
			return errors.New("health check failed")
		}
		return nil
	}

	fmt.Fprintf(out, "root: %s\n", report.Root)
	fmt.Fprintf(out, "title: %s\n", report.Config.Title)
	fmt.Fprintf(out, "host: %s\n", report.Config.Host)
	fmt.Fprintf(out, "content: %d pages, %d articles, %d drafts\n", report.Pages, report.Articles, report.Drafts)
	fmt.Fprintf(out, "site: %d menus, %d sections, %d widgets\n", report.Menus, report.Sections, report.Widgets)
	fmt.Fprintf(out, "public: %d files\n", report.PublicFiles)
	fmt.Fprintf(out, "health: %s (%d errors, %d warnings)\n", report.Health(), report.ErrorCount(), report.WarningCount())
	return nil
}

func newFlagSet(name string, errOut io.Writer) *flag.FlagSet {
	fs := flag.NewFlagSet(name, flag.ContinueOnError)
	fs.SetOutput(errOut)
	return fs
}

func parseInterspersed(fs *flag.FlagSet, args []string) ([]string, error) {
	var flagArgs []string
	var positionals []string

	for i := 0; i < len(args); i++ {
		arg := args[i]
		if !strings.HasPrefix(arg, "-") || arg == "-" {
			positionals = append(positionals, arg)
			continue
		}
		flagArgs = append(flagArgs, arg)
		name := strings.TrimLeft(arg, "-")
		if idx := strings.Index(name, "="); idx >= 0 {
			name = name[:idx]
		}
		f := fs.Lookup(name)
		if f == nil {
			return nil, fmt.Errorf("unknown flag %s", arg)
		}
		if isBoolFlag(f) || strings.Contains(arg, "=") {
			continue
		}
		if i+1 >= len(args) {
			return nil, fmt.Errorf("flag %s requires a value", arg)
		}
		i++
		flagArgs = append(flagArgs, args[i])
	}

	if err := fs.Parse(flagArgs); err != nil {
		return nil, err
	}
	return positionals, nil
}

func isBoolFlag(f *flag.Flag) bool {
	type boolFlag interface {
		IsBoolFlag() bool
	}
	value, ok := f.Value.(boolFlag)
	return ok && value.IsBoolFlag()
}

func splitCSV(value string) []string {
	if value == "" {
		return nil
	}
	var out []string
	for _, part := range strings.Split(value, ",") {
		part = strings.TrimSpace(part)
		if part != "" {
			out = append(out, part)
		}
	}
	return out
}

func requiresTarget(kind string) bool {
	switch kind {
	case "page", "section", "article", "custom":
		return true
	default:
		return false
	}
}
