package importer

import (
	"bytes"
	"os"
	"owl-blogs/app"
	entrytypes "owl-blogs/entry_types"
	"path"
	"time"

	"gopkg.in/yaml.v2"
)

type ReplyData struct {
	Url  string `yaml:"url"`
	Text string `yaml:"text"`
}
type BookmarkData struct {
	Url  string `yaml:"url"`
	Text string `yaml:"text"`
}

type RecipeData struct {
	Yield       string   `yaml:"yield"`
	Duration    string   `yaml:"duration"`
	Ingredients []string `yaml:"ingredients"`
}

type PostMeta struct {
	Type        string       `yaml:"type"`
	Title       string       `yaml:"title"`
	Description string       `yaml:"description"`
	Aliases     []string     `yaml:"aliases"`
	Date        time.Time    `yaml:"date"`
	Draft       bool         `yaml:"draft"`
	Reply       ReplyData    `yaml:"reply"`
	Bookmark    BookmarkData `yaml:"bookmark"`
	Recipe      RecipeData   `yaml:"recipe"`
	PhotoPath   string       `yaml:"photo"`
}

type Post struct {
	Id      string
	Meta    PostMeta
	Content string
}

func (post *Post) MediaDir() string {
	return path.Join("public", post.Id, "media")
}

func (pm *PostMeta) UnmarshalYAML(unmarshal func(interface{}) error) error {
	type T struct {
		Type        string       `yaml:"type"`
		Title       string       `yaml:"title"`
		Description string       `yaml:"description"`
		Aliases     []string     `yaml:"aliases"`
		Draft       bool         `yaml:"draft"`
		Reply       ReplyData    `yaml:"reply"`
		Bookmark    BookmarkData `yaml:"bookmark"`
		Recipe      RecipeData   `yaml:"recipe"`
		PhotoPath   string       `yaml:"photo"`
	}
	type S struct {
		Date string `yaml:"date"`
	}

	var t T
	var s S
	if err := unmarshal(&t); err != nil {
		return err
	}
	if err := unmarshal(&s); err != nil {
		return err
	}

	pm.Type = t.Type
	if pm.Type == "" {
		pm.Type = "article"
	}
	pm.Title = t.Title
	pm.Description = t.Description
	pm.Aliases = t.Aliases
	pm.Draft = t.Draft
	pm.Reply = t.Reply
	pm.Bookmark = t.Bookmark
	pm.Recipe = t.Recipe
	pm.PhotoPath = t.PhotoPath

	possibleFormats := []string{
		"2006-01-02",
		time.Layout,
		time.ANSIC,
		time.UnixDate,
		time.RubyDate,
		time.RFC822,
		time.RFC822Z,
		time.RFC850,
		time.RFC1123,
		time.RFC1123Z,
		time.RFC3339,
		time.RFC3339Nano,
		time.Stamp,
		time.StampMilli,
		time.StampMicro,
		time.StampNano,
	}

	for _, format := range possibleFormats {
		if t, err := time.Parse(format, s.Date); err == nil {
			pm.Date = t
			break
		}
	}

	return nil
}

func LoadContent(data []byte) string {

	// trim yaml block
	// TODO this can be done nicer
	trimmedData := bytes.TrimSpace(data)
	// ensure that data ends with a newline
	trimmedData = append(trimmedData, []byte("\n")...)
	// check first line is ---
	if string(trimmedData[0:4]) == "---\n" {
		trimmedData = trimmedData[4:]
		// find --- end
		end := bytes.Index(trimmedData, []byte("\n---\n"))
		if end != -1 {
			data = trimmedData[end+5:]
		}
	}

	return string(data)
}

func LoadMeta(data []byte) (PostMeta, error) {

	// get yaml metadata block
	meta := PostMeta{}
	trimmedData := bytes.TrimSpace(data)
	// ensure that data ends with a newline
	trimmedData = append(trimmedData, []byte("\n")...)
	// check first line is ---
	if string(trimmedData[0:4]) == "---\n" {
		trimmedData = trimmedData[4:]
		// find --- end
		end := bytes.Index(trimmedData, []byte("---\n"))
		if end != -1 {
			metaData := trimmedData[:end]
			err := yaml.Unmarshal(metaData, &meta)
			if err != nil {
				return PostMeta{}, err
			}
		}
	}

	return meta, nil
}

func AllUserPosts(userPath string) ([]Post, error) {
	postFiles := ListDir(path.Join(userPath, "public"))
	posts := make([]Post, 0)
	for _, id := range postFiles {
		// if is a directory and has index.md, add to posts
		if dirExists(path.Join(userPath, "public", id)) {
			if fileExists(path.Join(userPath, "public", id, "index.md")) {
				postData, err := os.ReadFile(path.Join(userPath, "public", id, "index.md"))
				if err != nil {
					return nil, err
				}
				meta, err := LoadMeta(postData)
				if err != nil {
					return nil, err
				}
				post := Post{
					Id:      id,
					Content: LoadContent(postData),
					Meta:    meta,
				}
				posts = append(posts, post)
			}
		}
	}

	return posts, nil
}

func ListDir(path string) []string {
	dir, _ := os.Open(path)
	defer dir.Close()
	files, _ := dir.Readdirnames(-1)
	return files
}

func dirExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func ConvertTypeList(v1 []string, registry *app.EntryTypeRegistry) []string {
	v2 := make([]string, len(v1))
	for i, v1Type := range v1 {
		switch v1Type {
		case "article":
			name, _ := registry.TypeName(&entrytypes.Article{})
			v2[i] = name
		case "bookmark":
			name, _ := registry.TypeName(&entrytypes.Bookmark{})
			v2[i] = name
		case "reply":
			name, _ := registry.TypeName(&entrytypes.Reply{})
			v2[i] = name
		case "photo":
			name, _ := registry.TypeName(&entrytypes.Image{})
			v2[i] = name
		case "note":
			name, _ := registry.TypeName(&entrytypes.Note{})
			v2[i] = name
		case "recipe":
			name, _ := registry.TypeName(&entrytypes.Recipe{})
			v2[i] = name
		case "page":
			name, _ := registry.TypeName(&entrytypes.Page{})
			v2[i] = name
		default:
			v2[i] = v1Type
		}
	}
	return v2
}