package i18n

import (
	"errors"
	"fmt"
	"html/template"
	"strings"
	"sync"

	"git.ma-al.com/goc_daniel/b2b/app/model"
	"github.com/gofiber/fiber/v3"
)

type I18nTranslation string

type TranslationResponse map[uint]map[string]map[string]map[string]string

var TransStore = newTranslationsStore()

var (
	ErrLangIsoEmpty   = errors.New("lang_id_empty")
	ErrScopeEmpty     = errors.New("scope_empty")
	ErrComponentEmpty = errors.New("component_empty")
	ErrKeyEmpty       = errors.New("key_empty")

	ErrLangIsoNotFoundInCache   = errors.New("lang_id_not_in_cache")
	ErrScopeNotFoundInCache     = errors.New("scope_not_in_cache")
	ErrComponentNotFoundInCache = errors.New("component_not_in_cache")
	ErrKeyNotFoundInCache       = errors.New("key_invalid_in_cache")
)

type TranslationsStore struct {
	mutex sync.RWMutex
	cache TranslationResponse
}

func newTranslationsStore() *TranslationsStore {
	service := &TranslationsStore{
		cache: make(TranslationResponse),
	}

	return service
}

func (s *TranslationsStore) Get(langID uint, scope string, component string, key string) (string, error) {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	if langID == 0 {
		return "lang_id_empty", ErrLangIsoEmpty
	}

	if _, ok := s.cache[langID]; !ok {
		return fmt.Sprintf("lang_id_not_in_cache: %d", langID), ErrLangIsoNotFoundInCache
	}

	if scope == "" {
		return "scope_empty", ErrScopeEmpty
	}

	if _, ok := s.cache[langID][scope]; !ok {
		return fmt.Sprintf("scope_not_in_cache: %s", scope), ErrScopeNotFoundInCache
	}

	if component == "" {
		return "component_empty", ErrComponentEmpty
	}

	if _, ok := s.cache[langID][scope][component]; !ok {
		return fmt.Sprintf("component_not_in_cache: %s", component), ErrComponentNotFoundInCache
	}

	if key == "" {
		return "key_empty", ErrKeyEmpty
	}

	if _, ok := s.cache[langID][scope][component][key]; !ok {
		return fmt.Sprintf("key_invalid_in_cache: %s", key), ErrKeyNotFoundInCache
	}

	return s.cache[langID][scope][component][key], nil
}
func (s *TranslationsStore) GetTranslations(langID uint, scope string, components []string) (*TranslationResponse, error) {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	if langID == 0 {
		resp := make(TranslationResponse)
		for k, v := range s.cache {
			resp[k] = v
		}
		return &resp, nil
	}

	if _, ok := s.cache[langID]; !ok {
		return nil, fmt.Errorf("invalid translation arguments")
	}

	tr := make(TranslationResponse)

	if scope == "" {
		tr[langID] = s.cache[langID]
		return &tr, nil
	}

	if _, ok := s.cache[langID][scope]; !ok {
		return nil, fmt.Errorf("invalid translation arguments")
	}

	tr[langID] = make(map[string]map[string]map[string]string)
	if len(components) <= 0 {
		tr[langID][scope] = s.cache[langID][scope]
		return &tr, nil
	}

	tr[langID][scope] = make(map[string]map[string]string)

	var invalidComponents []string
	for _, component := range components {
		if _, ok := s.cache[langID][scope][component]; !ok {
			invalidComponents = append(invalidComponents, component)
			continue
		}
		tr[langID][scope][component] = s.cache[langID][scope][component]
	}
	if len(invalidComponents) > 0 {
		return &tr, fmt.Errorf("invalid translation arguments")
	}

	return &tr, nil
}

// GetAllTranslations returns all translations from the cache
func (s *TranslationsStore) GetAllTranslations() *TranslationResponse {
	s.mutex.RLock()
	defer s.mutex.RUnlock()

	resp := make(TranslationResponse)
	for k, v := range s.cache {
		resp[k] = v
	}
	return &resp
}

func (s *TranslationsStore) LoadTranslations(translations []model.Translation) error {

	s.mutex.Lock()
	defer s.mutex.Unlock()
	s.cache = make(TranslationResponse)
	for _, t := range translations {
		lang := uint(t.LangID)
		scp := t.Scope.Name
		cmp := t.Component.Name
		data := ""
		if t.Data != nil {
			data = *t.Data
		}

		if _, ok := s.cache[lang]; !ok {
			s.cache[lang] = make(map[string]map[string]map[string]string)
		}
		if _, ok := s.cache[lang][scp]; !ok {
			s.cache[lang][scp] = make(map[string]map[string]string)
		}
		if _, ok := s.cache[lang][scp][cmp]; !ok {
			s.cache[lang][scp][cmp] = make(map[string]string)
		}
		s.cache[lang][scp][cmp][t.Key] = data
	}
	return nil
}

// ReloadTranslations reloads translations from the database
func (s *TranslationsStore) ReloadTranslations(translations []model.Translation) error {
	return s.LoadTranslations(translations)
}

// T_ is meant to be used to translate error messages and other system communicates.
func T_[T ~string](c fiber.Ctx, key T, params ...interface{}) string {
	if langID, ok := c.Locals("langID").(uint); ok {
		parts := strings.Split(string(key), ".")

		if len(parts) >= 2 {
			if trans, err := TransStore.Get(langID, "Backend", parts[0], strings.Join(parts[1:], ".")); err == nil {
				return Format(trans, params...)
			}
		} else {
			if trans, err := TransStore.Get(langID, "Backend", "be", parts[0]); err == nil {
				return Format(trans, params...)
			}
		}
	}

	return string(key)
}

// T___ works exactly the same as T_ but uses just language ID instead of the whole context
func T___[T ~string](langId uint, key T, params ...interface{}) string {
	parts := strings.Split(string(key), ".")

	if len(parts) >= 2 {
		if trans, err := TransStore.Get(langId, "Backend", parts[0], strings.Join(parts[1:], ".")); err == nil {
			return Format(trans, params...)
		}
	} else {
		if trans, err := TransStore.Get(langId, "Backend", "be", parts[0]); err == nil {
			return Format(trans, params...)
		}
	}

	return string(key)
}

func Format(text string, params ...interface{}) string {
	text = fmt.Sprintf(text, params...)
	return text
}

// T__ wraps T_ adding a conversion from string to template.HTML
func T__(c fiber.Ctx, key string, params ...interface{}) template.HTML {
	return template.HTML(T_(c, key, params...))
}

// MapKeyOnTranslationMap is a helper function to map keys on translation map
// this is used to map keys on translation map
//
//	 example:
//		map :=  map[T]string{}
//	 MapKeyOnTranslationMap(ctx, map, key1, key2, key3)
func MapKeyOnTranslationMap[T ~string](c fiber.Ctx, m *map[T]string, key ...T) {
	if *m == nil {
		*m = make(map[T]string)
	}
	for _, k := range key {
		(*m)[k] = T_(c, string(k))
	}
}
