package fs

import (
	"context"
	"fmt"
	"io/fs"
	"net/url"
	"time"

	"github.com/adrg/frontmatter"
	"gopkg.in/yaml.v3"

	"source.toby3d.me/toby3d/home/internal/domain"
	"source.toby3d.me/toby3d/home/internal/site"
)

type (
	Site struct {
		Title           string         `yaml:"title"`
		TimeZone        TimeZone       `yaml:"timeZone"`
		DefaultLanguage Language       `yaml:"defaultLanguage"`
		BaseURL         URL            `yaml:"baseUrl"`
		Params          map[string]any `yaml:",inline"`
		Content         []byte         `yaml:"-"`
	}

	TimeZone struct {
		*time.Location `yaml:"-"`
	}

	URL struct {
		*url.URL `yaml:"-"`
	}

	Language struct {
		domain.Language `yaml:"-"`
	}

	fileSystemSiteRepository struct {
		dir fs.FS
	}
)

var FrontMatterFormats = []*frontmatter.Format{
	frontmatter.NewFormat(`---`, `---`, yaml.Unmarshal),
}

func NewFileSystemSiteRepository(rootDir fs.FS) site.Repository {
	return &fileSystemSiteRepository{
		dir: rootDir,
	}
}

func (repo *fileSystemSiteRepository) Get(ctx context.Context, lang domain.Language) (*domain.Site, error) {
	ext := ".md"
	if lang != domain.LanguageUnd {
		ext = "." + lang.Lang() + ext
	}

	target := "index" + ext

	f, err := repo.dir.Open(target)
	if err != nil {
		return nil, fmt.Errorf("cannot open '%s' site file: %w", target, err)
	}
	defer f.Close()

	data := NewSite()
	if data.Content, err = frontmatter.Parse(f, data, FrontMatterFormats...); err != nil {
		return nil, fmt.Errorf("cannot parse site content as FrontMatter: %w", err)
	}

	return &domain.Site{
		File:            domain.NewPath(target),
		DefaultLanguage: data.DefaultLanguage.Language,
		Language:        lang,
		Title:           data.Title,
		BaseURL:         data.BaseURL.URL,
		TimeZone:        data.TimeZone.Location,
		Params:          data.Params,
		Languages:       make([]domain.Language, 0),
	}, nil
}

func (tz *TimeZone) UnmarshalYAML(value *yaml.Node) error {
	if value.IsZero() {
		tz.Location = time.UTC

		return nil
	}

	loc, err := time.LoadLocation(value.Value)
	if err != nil {
		return fmt.Errorf("cannot parse timeZone value '%v': %w", value, err)
	}

	tz.Location = loc

	return nil
}

func (tz TimeZone) MarshalYAML() (any, error) {
	if tz.Location == nil {
		return time.UTC.String(), nil
	}

	return tz.Location.String(), nil
}

func (u *URL) UnmarshalYAML(value *yaml.Node) error {
	if value.IsZero() {
		return nil
	}

	parsed, err := url.Parse(value.Value)
	if err != nil {
		return fmt.Errorf("cannot parse URL value '%v': %w", value, err)
	}

	u.URL = parsed

	return nil
}

func (u URL) MarshalYAML() (any, error) {
	if u.URL == nil {
		return nil, nil
	}

	return u.URL.String(), nil
}

func (l *Language) UnmarshalYAML(value *yaml.Node) error {
	if value.IsZero() {
		return nil
	}

	l.Language = domain.NewLanguage(value.Value)

	return nil
}

func (l Language) MarshalYAML() (any, error) {
	if l.Language == domain.LanguageUnd {
		return nil, nil
	}

	return l.Language.Lang(), nil
}

func NewSite() *Site {
	return &Site{
		Params: make(map[string]any),
	}
}
