package storyteller

// |@@| C

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"
	"testing"
)

type Storyteller struct {
	Data struct {
		PreviousKind string
	}
}

const (
	Reset   = "\033[0m"
	Blue    = "\033[94m"
	Magenta = "\033[95m"
	Cyan    = "\033[96m"
	Purple  = "\033[35m"
	Pink    = "\033[38;5;206m"
)

func (this *Storyteller) SetAutoScenario(ctx context.Context) {
	testingFunctionName := ctx.Value("t").(*testing.T).Name()
	name := this.convertFromPascalCaseToHumanCase(testingFunctionName)
	this.SetScenario(name, "")
}

func (this *Storyteller) SetScenario(scenario string, description string) {
	if this.isInWriteMode() {
		this.prepareFile()
	}
	this.bimodalPrintLn("  " + this.color(Magenta, "Scenario:") + " " + scenario)
	content := "  ---\n"
	content += "  " + description + "\n"
	content += "  ---"
	this.bimodalPrintLn(content)
}

func (this *Storyteller) Given(given string) {
	if this.samePreviousKind("given") {
		this.bimodalPrintLn(this.color(Blue, "       And ") + given)
		return
	}
	this.color(Blue, "")
	this.bimodalPrintLn(this.color(Blue, "    ➤ Given ") + given)
}

func (this *Storyteller) When(when string) {
	if this.samePreviousKind("when") {
		this.bimodalPrintLn(this.color(Blue, "       And ") + when)
		return
	}
	this.bimodalPrintLn(this.color(Blue, "    ➤ When ") + when)
}

func (this *Storyteller) Then(then string) {
	if this.samePreviousKind("then") {
		this.bimodalPrintLn(this.color(Cyan, "       And ") + then)
		return
	}
	this.bimodalPrintLn(this.color(Cyan, "    ➤ Then ") + then)
}

func (this *Storyteller) As(as string) {
	if this.samePreviousKind("as") {
		this.bimodalPrintLn(this.color(Pink, "       And ") + as)
		return
	}
	this.bimodalPrintLn(this.color(Pink, "    ➤ As ") + as)
}

func (this *Storyteller) Also(also string) {
	if this.samePreviousKind("also") {
		this.bimodalPrintLn(this.color(Purple, "       And ") + also)
		return
	}
	this.bimodalPrintLn(this.color(Purple, "    ➤ Also ") + also)
}

func (this *Storyteller) bimodalPrintLn(line string) {
	if this.isInWriteMode() {
		this.writeInFile(line)
	}
	fmt.Println(line)
}

func (this *Storyteller) isInWriteMode() bool {
	return os.Getenv("SUP_STORYTELLER_WRITER") == "file"
}

func (this *Storyteller) writeInFile(line string) {
	filePath := this.filePath()

	file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		panic("cannot open file")
	}
	defer file.Close()

	if _, err := file.WriteString(line + "\n"); err != nil {
		panic(err)
	}
}

func (this *Storyteller) filePath() string {
	filename := this.testFilePath()
	if strings.Count(filename, "/tests/") > 1 {
		panic("Multiple occurrences of '/tests/' found in the path")
	}
	path := strings.Replace(filename, "/tests/", "/docs/stories/", 1)
	path = strings.TrimSuffix(path, ".go")
	path += ".txt"
	return path
}

func (this *Storyteller) testFilePath() string {
	for skip := 0; ; skip++ {
		_, file, _, ok := runtime.Caller(skip)
		if !ok {
			break
		}
		if strings.Count(file, "/tests/") > 0 {
			return file
		}
	}
	panic("Unable to detect test file path")
}

func (this *Storyteller) prepareFile() {
	storyFilePath := this.filePath()
	if err := os.MkdirAll(filepath.Dir(storyFilePath), os.ModePerm); err != nil {
		panic("cannot create file path")
	}
	file, err := os.OpenFile(storyFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
	if err != nil {
		panic("cannot open file")
	}
	defer file.Close()
}

func (this *Storyteller) samePreviousKind(kind string) bool {
	same := this.Data.PreviousKind == kind
	this.Data.PreviousKind = kind
	return same
}

func (this *Storyteller) color(color string, string string) string {
	if this.isInWriteMode() {
		return string
	}
	return color + string + Reset
}

func (this *Storyteller) convertFromPascalCaseToHumanCase(str string) string {
	re := regexp.MustCompile(`([A-Z][a-z]*)`)
	words := re.FindAllString(str, -1)
	if len(words) == 0 {
		return str
	}
	words[0] = strings.ToUpper(words[0][:1]) + strings.ToLower(words[0][1:])
	for i := 1; i < len(words); i++ {
		words[i] = strings.ToLower(words[i])
	}
	return strings.Join(words, " ")
}
