package sp

import (
	"context"

	cache "git.dmitriygnatenko.ru/dima/go-common/cache/ttl_memory_cache"
	"git.dmitriygnatenko.ru/dima/go-common/db"
	"git.dmitriygnatenko.ru/dima/go-common/logger"
	"git.dmitriygnatenko.ru/dima/go-common/smtp"

	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helper/errors"
	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/repository"
	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/auth"
	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/config"
	"git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/service/i18n"
)

type ServiceProvider struct {
	config               *config.Service
	auth                 *auth.Service
	cache                *cache.Cache
	tm                   *db.TxManager
	articleRepository    *repository.ArticleRepository
	tagRepository        *repository.TagRepository
	articleTagRepository *repository.ArticleTagRepository
	userRepository       *repository.UserRepository
	languageRepository   *repository.LanguageRepository
}

func Init() (*ServiceProvider, error) {
	sp := &ServiceProvider{}

	// Init services
	configService, err := config.Init()
	if err != nil {
		return nil, errors.Wrap(err, "can't init config")
	}
	sp.config = configService

	sp.cache = cache.NewCache(
		cache.NewConfig(
			cache.WithExpiration(configService.CacheDefaultDuration()),
		),
	)

	authService, err := auth.Init(sp.config)
	if err != nil {
		return nil, errors.Wrap(err, "can't init auth service")
	}
	sp.auth = authService

	dbService, err := db.NewDB(
		db.NewConfig(
			db.WithDriver(configService.DBDriver()),
			db.WithHost(configService.DBHost()),
			db.WithPort(configService.DBPort()),
			db.WithUsername(configService.DBUser()),
			db.WithPassword(configService.DBPassword()),
			db.WithDatabase(configService.DBName()),
			db.WithMaxIdleConns(configService.DBMaxIdleConns()),
			db.WithMaxOpenConns(configService.DBMaxOpenConns()),
			db.WithMaxIdleConnLifetime(configService.DBMaxOpenConnLifetime()),
			db.WithMaxOpenConnLifetime(configService.DBMaxOpenConnLifetime()),
		),
	)

	if err != nil {
		return nil, errors.Wrap(err, "can't init DB service")
	}

	// Init transaction manager
	sp.tm = db.NewTransactionManager(dbService)

	// Init repositories
	sp.articleRepository = repository.InitArticleRepository(dbService)
	sp.tagRepository = repository.InitTagRepository(dbService)
	sp.articleTagRepository = repository.InitArticleTagRepository(dbService)
	sp.userRepository = repository.InitUserRepository(dbService)
	sp.languageRepository = repository.InitLanguageRepository(dbService)

	// Init logger
	loggerConfigOptions := logger.ConfigOptions{
		logger.WithEmailLogEnabled(configService.LoggerEmailEnabled()),
		logger.WithEmailLogLevel(configService.LoggerEmailLevel()),
		logger.WithEmailSubject(configService.LoggerEmailSubject()),
		logger.WithEmailRecipient(configService.LoggerEmailRecipient()),
		logger.WithStdoutLogEnabled(configService.LoggerStdoutEnabled()),
		logger.WithStdoutLogLevel(configService.LoggerStdoutLevel()),
		logger.WithFileLogEnabled(configService.LoggerFileEnabled()),
		logger.WithFileLogLevel(configService.LoggerFileLevel()),
		logger.WithFilepath(configService.LoggerFilePath()),
	}

	if len(configService.SMTPPassword()) > 0 &&
		len(configService.SMTPUser()) > 0 &&
		len(configService.SMTPHost()) > 0 &&
		configService.SMTPPort() > 0 {

		smtpService, smtpErr := smtp.NewSMTP(
			smtp.NewConfig(
				smtp.WithPassword(configService.SMTPPassword()),
				smtp.WithUsername(configService.SMTPUser()),
				smtp.WithHost(configService.SMTPHost()),
				smtp.WithPort(configService.SMTPPort()),
			),
		)
		if smtpErr != nil {
			return nil, errors.Wrap(smtpErr, "can't init SMTP service")
		}

		loggerConfigOptions.Add(logger.WithSMTPClient(smtpService))
	}

	err = logger.Init(logger.NewConfig(loggerConfigOptions...))
	if err != nil {
		return nil, errors.Wrap(err, "can't init logger service")
	}

	languages, err := sp.languageRepository.GetAll(context.Background())
	if err != nil {
		return nil, errors.Wrap(err, "can't get languages")
	}

	// Init translations
	if err = i18n.Init(languages); err != nil {
		return nil, errors.Wrap(err, "can't init I18N service")
	}

	return sp, nil
}

func (sp *ServiceProvider) ConfigService() *config.Service {
	return sp.config
}

func (sp *ServiceProvider) CacheService() *cache.Cache {
	return sp.cache
}

func (sp *ServiceProvider) AuthService() *auth.Service {
	return sp.auth
}

func (sp *ServiceProvider) TransactionManager() *db.TxManager {
	return sp.tm
}

func (sp *ServiceProvider) ArticleRepository() *repository.ArticleRepository {
	return sp.articleRepository
}

func (sp *ServiceProvider) TagRepository() *repository.TagRepository {
	return sp.tagRepository
}

func (sp *ServiceProvider) ArticleTagRepository() *repository.ArticleTagRepository {
	return sp.articleTagRepository
}

func (sp *ServiceProvider) UserRepository() *repository.UserRepository {
	return sp.userRepository
}

func (sp *ServiceProvider) LanguageRepository() *repository.LanguageRepository {
	return sp.languageRepository
}
