package cli

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"
	"time"
	"unicode"
)

func statusCommand(status string) string {
	if status == "published" {
		return "publish"
	}
	return "unpublish"
}

func uniquePath(path string) string {
	if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
		return path
	}

	ext := filepath.Ext(path)
	base := strings.TrimSuffix(path, ext)
	for i := 2; i < 1000; i++ {
		candidate := fmt.Sprintf("%s-%d%s", base, i, ext)
		if _, err := os.Stat(candidate); errors.Is(err, os.ErrNotExist) {
			return candidate
		}
	}
	return fmt.Sprintf("%s-%d%s", base, time.Now().Unix(), ext)
}

func mustRel(root string, path string) string {
	rel, err := filepath.Rel(root, path)
	if err != nil {
		return path
	}
	return rel
}

func stringPtrValue(value *string) string {
	if value == nil {
		return ""
	}
	return *value
}

func parseTime(value string) (time.Time, bool) {
	value = strings.TrimSpace(value)
	if value == "" {
		return time.Time{}, false
	}
	parsed, err := time.Parse(time.RFC3339, value)
	if err == nil {
		return parsed, true
	}
	parsed, err = time.Parse("2006-01-02 15:04:05", value)
	if err == nil {
		return parsed, true
	}
	return time.Time{}, false
}

func confirm(prompt string) bool {
	fmt.Fprintf(os.Stderr, "%s [y/N] ", prompt)
	var answer string
	if _, err := fmt.Fscan(os.Stdin, &answer); err != nil {
		return false
	}
	answer = strings.ToLower(strings.TrimSpace(answer))
	return answer == "y" || answer == "yes"
}

func resolveRootPath(root string, path string) string {
	if filepath.IsAbs(path) {
		return path
	}
	return filepath.Join(root, path)
}

func yesNo(value bool) string {
	if value {
		return "yes"
	}
	return "no"
}

func boolInt(value bool) int {
	if value {
		return 1
	}
	return 0
}

func contentHash(content string) string {
	sum := sha256.Sum256([]byte(content))
	return "sha256:" + hex.EncodeToString(sum[:])
}

func sortedKeys(values map[string]int) []string {
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func sortedBoolKeys(values map[string]bool) []string {
	keys := make([]string, 0, len(values))
	for key := range values {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return keys
}

func uniqueSlug(root string, slug string) (string, error) {
	base := sanitizeSlug(slug)
	if base == "" {
		return "", errors.New("empty slug")
	}
	if !slugExists(root, base) {
		return base, nil
	}

	for i := 2; i < 1000; i++ {
		candidate := fmt.Sprintf("%s-%d", base, i)
		if !slugExists(root, candidate) {
			return candidate, nil
		}
	}
	return "", fmt.Errorf("could not find available slug for %q", base)
}

func slugExists(root string, slug string) bool {
	posts, err := loadPosts(root)
	if err == nil {
		for _, post := range posts {
			fileSlug := strings.TrimSuffix(filepath.Base(post.Path), ".md")
			if post.Frontmatter.Slug == slug || fileSlug == slug {
				return true
			}
		}
	}

	_, err = os.Stat(filepath.Join(root, defaultPostsDir, slug+".md"))
	return err == nil
}

func uniquePostPath(postsDir string, slug string) (string, error) {
	candidate := filepath.Join(postsDir, slug+".md")
	if _, err := os.Stat(candidate); errors.Is(err, os.ErrNotExist) {
		return candidate, nil
	} else if err != nil {
		return "", err
	}

	for i := 2; i < 1000; i++ {
		candidate = filepath.Join(postsDir, fmt.Sprintf("%s-%d.md", slug, i))
		if _, err := os.Stat(candidate); errors.Is(err, os.ErrNotExist) {
			return candidate, nil
		} else if err != nil {
			return "", err
		}
	}
	return "", fmt.Errorf("could not find available filename for slug %q", slug)
}

func findProjectRoot() (string, error) {
	wd, err := os.Getwd()
	if err != nil {
		return "", err
	}

	for {
		if isProjectRoot(wd) {
			return wd, nil
		}
		parent := filepath.Dir(wd)
		if parent == wd {
			return "", errors.New("could not find project root")
		}
		wd = parent
	}
}

func isProjectRoot(dir string) bool {
	if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
		if _, err := os.Stat(filepath.Join(dir, "backend", "go.mod")); err == nil {
			return true
		}
	}
	if _, err := os.Stat(filepath.Join(dir, "backend", "cmd", "osaetctl")); err == nil {
		if _, err := os.Stat(filepath.Join(dir, "frontend", "site", "package.json")); err == nil {
			return true
		}
	}
	return false
}

func fallbackSlug(title string) string {
	var words []string
	var b strings.Builder

	flush := func() {
		if b.Len() > 0 {
			words = append(words, b.String())
			b.Reset()
		}
	}

	for _, r := range strings.ToLower(title) {
		switch {
		case r >= 'a' && r <= 'z':
			b.WriteRune(r)
		case r >= '0' && r <= '9':
			b.WriteRune(r)
		case unicode.IsSpace(r) || r == '-' || r == '_' || r == '/':
			flush()
		default:
			flush()
		}
	}
	flush()

	if len(words) == 0 {
		return "post-" + time.Now().Format("20060102150405")
	}
	return sanitizeSlug(strings.Join(words, "-"))
}

func sanitizeSlug(slug string) string {
	slug = strings.ToLower(strings.TrimSpace(slug))
	re := regexp.MustCompile(`[^a-z0-9]+`)
	slug = re.ReplaceAllString(slug, "-")
	slug = strings.Trim(slug, "-")
	for strings.Contains(slug, "--") {
		slug = strings.ReplaceAll(slug, "--", "-")
	}
	if len(slug) > 80 {
		slug = strings.Trim(slug[:80], "-")
	}
	return slug
}

func randomID() string {
	var b [16]byte
	if _, err := rand.Read(b[:]); err != nil {
		return fmt.Sprintf("post-%d", time.Now().UnixNano())
	}
	return hex.EncodeToString(b[:])
}

func escapeYAML(s string) string {
	s = strings.ReplaceAll(s, `\`, `\\`)
	s = strings.ReplaceAll(s, `"`, `\"`)
	return s
}

func formatYAMLStringList(values []string) string {
	if len(values) == 0 {
		return "[]"
	}

	var b strings.Builder
	b.WriteString("[")
	for i, value := range values {
		if i > 0 {
			b.WriteString(", ")
		}
		b.WriteString(`"`)
		b.WriteString(escapeYAML(value))
		b.WriteString(`"`)
	}
	b.WriteString("]")
	return b.String()
}

func writeFileIfMissing(path string, content string) error {
	if _, err := os.Stat(path); err == nil {
		return nil
	} else if !errors.Is(err, os.ErrNotExist) {
		return err
	}
	return os.WriteFile(path, []byte(content), 0o644)
}
