package utils

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"text/template"

	"gopkg.in/yaml.v2"
)

type FrontMatter struct {
	Title    string  `yaml:"title"`
	Rate     float64 `yaml:"rate"`
	Progress string  `yaml:"progress"`
	Episode  string  `yaml:"episode"`
	Date     string  `yaml:"date"`
}

func GenerateMarkdown(templatePath string, outputPath string, data map[string]interface{}) error {
	// Define a template funcition that generates image versions
	funcMap := template.FuncMap{
		"imageSize": func(imagePath string, size string) string {
			base := strings.TrimSuffix(imagePath, filepath.Ext(imagePath))
			return fmt.Sprintf("%s-%s.jpg", base, size)
		},
	}

	tmpl, err := template.New(filepath.Base(templatePath)).Funcs(funcMap).ParseFiles(templatePath)
	if err != nil {
		return err
	}

	var output bytes.Buffer
	err = tmpl.Execute(&output, data)
	if err != nil {
		return err
	}

	return os.WriteFile(outputPath, output.Bytes(), 0644)
}

// LoadMarkdown loads the frontmatter and content from an existing markdown file
func LoadMarkdown(filepath string) (FrontMatter, string, error) {
	var frontmatter FrontMatter
	var contentBuilder strings.Builder
	inFrontmatter := false
	frontmatterComplete := false

	file, err := os.Open(filepath)
	if err != nil {
		return frontmatter, "", err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()

		// Detect the start and end of the frontmatter block
		if line == "---" && !inFrontmatter {
			inFrontmatter = true
			continue
		} else if line == "---" && inFrontmatter {
			frontmatterComplete = true
			continue
		}

		if inFrontmatter && !frontmatterComplete {
			// Continue reading the frontmatter
			contentBuilder.WriteString(line + "\n")
		} else if frontmatterComplete {
			// Read the rest of the markdown content
			contentBuilder.WriteString(line + "\n")
		}
	}

	// Unmarshal frontmatter into the struct
	if err := yaml.Unmarshal([]byte(contentBuilder.String()), &frontmatter); err != nil {
		return frontmatter, "", fmt.Errorf("error unmarshalling frontmatter: %v", err)
	}

	// Read the markdown content
	contentBytes, err := ioutil.ReadAll(file)
	if err != nil {
		return frontmatter, "", err
	}

	return frontmatter, string(contentBytes), nil
}

// SaveUpdatedMarkdown updates only the specific fields in the frontmatter without altering the content
func SaveUpdatedMarkdown(filepath string, frontmatter FrontMatter, content string) error {
	// Read the entire file as a string
	fileBytes, err := ioutil.ReadFile(filepath)
	if err != nil {
		return err
	}

	// Convert file content to string
	fileContent := string(fileBytes)

	fieldPatterns := map[string]string{
		"progress": `(?m)^progress: (.*)%$`,
		"episode":  `(?m)^episode: (.*)$`,
		"date":     `(?m)^date: (.*)$`,
		"rate":     `(?m)^rate: (\d+\.?\d*)$`,
	}

	// Fields replacement
	fileContent = updateFieldInFrontmatter(fileContent, "progress", fmt.Sprintf("%s", frontmatter.Progress), fieldPatterns["progress"])
	fileContent = updateFieldInFrontmatter(fileContent, "episode", fmt.Sprintf("%s", frontmatter.Episode), fieldPatterns["episode"])
	fileContent = updateFieldInFrontmatter(fileContent, "date", fmt.Sprintf("%s", frontmatter.Date), fieldPatterns["date"])
	fileContent = updateFieldInFrontmatterFloat(fileContent, "rate", frontmatter.Rate, fieldPatterns["rate"])

	// Write the updated content back to the file
	err = ioutil.WriteFile(filepath, []byte(fileContent), 0644)
	if err != nil {
		return err
	}

	return nil
}

func updateFieldInFrontmatter(content, field, newValue, pattern string) string {
	re := regexp.MustCompile(pattern)
	return re.ReplaceAllString(content, fmt.Sprintf("%s: %s", field, newValue))
}

// Helper function to update a float64 field in the frontmatter (for numerical values like rate)
func updateFieldInFrontmatterFloat(fileContent string, field string, newValue float64, pattern string) string {
	re := regexp.MustCompile(pattern)
	updatedContent := re.ReplaceAllString(fileContent, fmt.Sprintf("%s: %.1f", field, newValue))
	return updatedContent
}