package cli

import (
	"bytes"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"gopkg.in/yaml.v3"
)

func loadPosts(root string) ([]postFile, error) {
	postsDir := filepath.Join(root, defaultPostsDir)
	entries, err := os.ReadDir(postsDir)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil, nil
		}
		return nil, err
	}

	var posts []postFile
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
			continue
		}

		post, err := readPostFile(filepath.Join(postsDir, entry.Name()))
		if err != nil {
			return nil, err
		}
		posts = append(posts, post)
	}
	return posts, nil
}

func loadPostBySlug(root string, slug string) (postFile, error) {
	cleanSlug := sanitizeSlug(slug)
	if cleanSlug == "" {
		return postFile{}, errors.New("missing slug")
	}

	posts, err := loadPosts(root)
	if err != nil {
		return postFile{}, err
	}
	for _, post := range posts {
		fileSlug := strings.TrimSuffix(filepath.Base(post.Path), ".md")
		if post.Frontmatter.Slug == cleanSlug || fileSlug == cleanSlug {
			return post, nil
		}
	}
	return postFile{}, fmt.Errorf("post not found: %s", cleanSlug)
}

func readPostFile(path string) (postFile, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return postFile{}, err
	}

	frontmatter, body, err := splitFrontmatter(data)
	if err != nil {
		return postFile{}, fmt.Errorf("%s: %w", path, err)
	}

	var meta postFrontmatter
	if err := yaml.Unmarshal(frontmatter, &meta); err != nil {
		return postFile{}, fmt.Errorf("%s: %w", path, err)
	}
	if meta.Slug == "" {
		meta.Slug = strings.TrimSuffix(filepath.Base(path), ".md")
	}
	if meta.Status == "" {
		meta.Status = "draft"
	}

	return postFile{
		Path:        path,
		Frontmatter: meta,
		Body:        strings.TrimPrefix(string(body), "\n"),
	}, nil
}

func writePostFile(post postFile) error {
	var frontmatter bytes.Buffer
	encoder := yaml.NewEncoder(&frontmatter)
	encoder.SetIndent(2)
	if err := encoder.Encode(post.Frontmatter); err != nil {
		return err
	}
	if err := encoder.Close(); err != nil {
		return err
	}

	var output bytes.Buffer
	output.WriteString("---\n")
	output.Write(frontmatter.Bytes())
	output.WriteString("---\n\n")
	output.WriteString(strings.TrimLeft(post.Body, "\n"))

	tmp := post.Path + ".tmp"
	if err := os.WriteFile(tmp, output.Bytes(), 0o644); err != nil {
		return err
	}
	return os.Rename(tmp, post.Path)
}

func splitFrontmatter(data []byte) ([]byte, []byte, error) {
	if !bytes.HasPrefix(data, []byte("---\n")) {
		return nil, nil, errors.New("missing frontmatter opening marker")
	}

	rest := data[len("---\n"):]
	idx := bytes.Index(rest, []byte("\n---"))
	if idx < 0 {
		return nil, nil, errors.New("missing frontmatter closing marker")
	}

	frontmatter := rest[:idx]
	body := rest[idx+len("\n---"):]
	if bytes.HasPrefix(body, []byte("\r\n")) {
		body = body[2:]
	} else if bytes.HasPrefix(body, []byte("\n")) {
		body = body[1:]
	}
	return frontmatter, body, nil
}
