package lclrimp

import (
	"encoding/json"
	"path"

	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mutil/fr"
	"github.com/nicksnyder/go-i18n/v2/i18n"
	"go.uber.org/zap"
	"golang.org/x/text/language"
)

type Lang struct {
	bundle    *i18n.Bundle
	localizer *i18n.Localizer
}

type Localizers = map[string]Lang

type Localizer struct {
	logger      mlogger.Logger
	l9rs        Localizers
	support     string
	serviceName string
}

type Config struct {
	Path        string   `yaml:"path"`
	Langs       []string `yaml:"languages"`
	Support     string   `yaml:"support"`
	ServiceName string   `yaml:"service_name"`
}

func loadBundleLocalization(logger mlogger.Logger, bundle *i18n.Bundle, localizationPath string) error {
	b, err := fr.ReadFile(logger, localizationPath)
	if err != nil {
		logger.Error("Failed to read localization", zap.Error(err), zap.String("localization_path", localizationPath))
		return err
	}
	_, err = bundle.ParseMessageFileBytes(b, localizationPath)
	if err != nil {
		logger.Error("Failed to parse localization", zap.Error(err), zap.String("localization_path", localizationPath))
		return err
	}

	return err
}

func loadLocalizations(logger mlogger.Logger, source string) (*i18n.Bundle, error) {
	bundle := i18n.NewBundle(language.English)

	// Register a json unmarshal function for i18n bundle.
	bundle.RegisterUnmarshalFunc("json", json.Unmarshal)

	// Load translations from json files for non-default languages.
	err := loadBundleLocalization(logger, bundle, source)
	if err != nil {
		// will not log error once again, just return nil
		return nil, err
	}

	return bundle, nil
}

func newLang(logger mlogger.Logger, language string, source string) (*Lang, error) {
	var lang Lang
	var err error
	lang.bundle, err = loadLocalizations(logger, source)
	if err != nil {
		logger.Error("Failed to install language bundle", zap.Error(err),
			zap.String("language", language), zap.String("source", source))
		return nil, err
	}

	lang.localizer = i18n.NewLocalizer(lang.bundle, language)
	if lang.localizer != nil {
		logger.Info("Installed language bundle",
			zap.String("language", language), zap.String("source", source))
	} else {
		logger.Error("Failed to install language bundle", zap.String("language", language), zap.String("source", source))
		return nil, merrors.Internal("failed_to_load_localization")
	}

	return &lang, nil
}

func prepareLocalizers(logger mlogger.Logger, conf *Config) (Localizers, error) {
	localizers := make(Localizers)
	for _, lang := range conf.Langs {
		path := path.Join(conf.Path, lang+".json")
		l, err := newLang(logger, lang, path)
		if err != nil {
			logger.Error("Failed to load localization", zap.Error(err), zap.String("language", lang), zap.String("source", path))
			return localizers, err
		}
		localizers[lang] = *l
	}
	return localizers, nil
}

func (loc *Localizer) LocalizeTemplate(id string, templateData, ctr any, lang string) (string, error) {
	lclzr, found := loc.l9rs[lang]
	if !found {
		loc.logger.Info("Language not found, falling back to en", zap.String("message_id", id), zap.String("language", lang))
		lclzr = loc.l9rs["en"]
	}

	config := i18n.LocalizeConfig{
		MessageID:    id,
		TemplateData: templateData,
		PluralCount:  ctr,
	}
	localized, err := lclzr.localizer.Localize(&config)
	if err != nil {
		loc.logger.Warn("Failed to localize string", zap.Error(err), zap.String("message_id", id), zap.String("language", lang))
	}

	return localized, err
}

func (loc *Localizer) LocalizeString(id string, lang string) (string, error) {
	return loc.LocalizeTemplate(id, nil, nil, lang)
}

func (loc *Localizer) ServiceName() string {
	return loc.serviceName
}

func (loc *Localizer) SupportMail() string {
	return loc.support
}

// NewConnection creates a new database connection
func CreateLocalizer(logger mlogger.Logger, config *Config) (*Localizer, error) {
	p := new(Localizer)
	p.logger = logger.Named("localizer")
	var err error
	p.l9rs, err = prepareLocalizers(p.logger, config)
	if err != nil {
		logger.Warn("Failed to create localizer", zap.Error(err))
		return nil, err
	}
	p.serviceName = config.ServiceName
	p.support = config.Support

	logger.Info("Localizer is up", zap.String("service_name", p.serviceName), zap.String("support", p.support))

	return p, nil
}
