package config

import (
	"encoding/json"
	"log"
	"os"
	"strings"

	"github.com/knadh/koanf/parsers/toml/v2"
	"github.com/knadh/koanf/providers/env/v2"
	"github.com/knadh/koanf/providers/file"
	"github.com/knadh/koanf/providers/structs"
	"github.com/knadh/koanf/v2"
	"github.com/tidwall/pretty"
	"go.uber.org/fx"

	"github.com/1995parham/koochooloo/internal/infra/db"
	"github.com/1995parham/koochooloo/internal/infra/generator"
	"github.com/1995parham/koochooloo/internal/infra/logger"
	"github.com/1995parham/koochooloo/internal/infra/telemetry"
)

// prefix indicates environment variables prefix.
const prefix = "koochooloo_"

// Config holds all configurations.
type Config struct {
	fx.Out

	Database  db.Config        `json:"database"  koanf:"database"`
	Logger    logger.Config    `json:"logger"    koanf:"logger"`
	Telemetry telemetry.Config `json:"telemetry" koanf:"telemetry"`
	Generator generator.Config `json:"generator" koanf:"generator"`
}

func Provide() Config {
	k := koanf.New(".")

	// load default configuration from default function
	if err := k.Load(structs.Provider(Default(), "koanf"), nil); err != nil {
		log.Fatalf("error loading default: %s", err)
	}

	// load configuration from file
	if err := k.Load(file.Provider("config.toml"), toml.Parser()); err != nil {
		log.Printf("error loading config.toml: %s", err)
	}

	// load environment variables
	if err := k.Load(
		// replace __ with . in environment variables so you can reference field a in struct b
		// as a__b.
		env.Provider(prefix, env.Opt{
			Prefix: ".",
			TransformFunc: func(source string, value string) (string, any) {
				base := strings.ToLower(strings.TrimPrefix(source, prefix))

				return strings.ReplaceAll(base, "__", "."), value
			},
			EnvironFunc: os.Environ,
		},
		),
		nil,
	); err != nil {
		log.Printf("error loading environment variables: %s", err)
	}

	var instance Config
	if err := k.Unmarshal("", &instance); err != nil {
		log.Fatalf("error unmarshalling config: %s", err)
	}

	indent, err := json.MarshalIndent(instance, "", "\t")
	if err != nil {
		panic(err)
	}

	indent = pretty.Color(indent, nil)

	log.Printf(`
================ Loaded Configuration ================
%s
======================================================
	`, string(indent))

	return instance
}
