package app

import (
	"context"
	"errors"
	"fmt"
	"github.com/go-redis/redis/v8"
	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
	"github.com/skeris/appInit"
	"github.com/themakers/hlog"
	"go.uber.org/zap"
	"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/clients"
	"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
	"penahub.gitlab.yandexcloud.net/backend/quiz/worker/answerwc"
	"penahub.gitlab.yandexcloud.net/backend/quiz/worker/privilegewc"
	"penahub.gitlab.yandexcloud.net/backend/quiz/worker/senders"
	"penahub.gitlab.yandexcloud.net/backend/quiz/worker/workers/shortstat"
	"penahub.gitlab.yandexcloud.net/backend/quiz/worker/workers/timeout"
	"penahub.gitlab.yandexcloud.net/pena-services/customer/pkg/customer_clients"
	"time"
)

type App struct {
	logger *zap.Logger
	err    chan error
}

func (a App) GetLogger() *zap.Logger {
	return a.logger
}

func (a App) GetErr() chan error {
	return a.err
}

var (
	errInvalidOptions = errors.New("invalid options")
)

var zapOptions = []zap.Option{
	zap.AddCaller(),
	zap.AddCallerSkip(2),
	zap.AddStacktrace(zap.ErrorLevel),
}

var _ appInit.CommonApp = (*App)(nil)

type Options struct {
	ServiceName            string `env:"SERVICE_NAME" default:"squiz"`
	KafkaBroker            string `env:"KAFKA_BROKER" default:"localhost:6379"`
	KafkaTopic             string `env:"KAFKA_TOPIC" default:"test-topic"`
	LoggerProdMode         bool   `env:"IS_PROD_LOG" default:"false"`
	IsProd                 bool   `env:"IS_PROD" default:"false"`
	MinioEP                string `env:"MINIO_EP" default:"localhost:3002"`
	MinioAK                string `env:"MINIO_AK" default:"minio"`
	MinioSK                string `env:"MINIO_SK" default:"miniostorage"`
	PostgresCredentials    string `env:"PG_CRED" default:"host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
	RedisHost              string `env:"REDIS_HOST" default:"localhost:6379"`
	RedisPassword          string `env:"REDIS_PASSWORD" default:"admin"`
	RedisDB                uint64 `env:"REDIS_DB" default:"2"`
	SmtpHost               string `env:"SMTP_HOST" default:"connect.mailclient.bz"`
	SmtpPort               string `env:"SMTP_PORT" default:"587"`
	SmtpSender             string `env:"SMTP_SENDER" default:"noreply@mailing.pena.digital"`
	SmtpUsername           string `env:"SMTP_USERNAME" default:"kotilion.95@gmail.com"`
	SmtpPassword           string `env:"SMTP_PASSWORD" default:"vWwbCSg4bf0p"`
	SmtpApiKey             string `env:"SMTP_API_KEY" default:"P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev"`
	SmtpApiUrl             string `env:"SMTP_API_URL" default:"https://api.smtp.bz/v1/smtp/send"`
	CustomerServiceAddress string `env:"CUSTOMER_SERVICE_ADDRESS" default:"localhost:9001"`
	TgToken                string `env:"TG_TOKEN"`
}

func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.CommonApp, error) {
	var (
		err, workerErr error
		zapLogger      *zap.Logger
		errChan        = make(chan error)
		options        Options
		ok             bool
	)

	if options, ok = opts.(Options); !ok {
		return App{}, errInvalidOptions
	}

	if options.LoggerProdMode {
		zapLogger, err = zap.NewProduction(zapOptions...)
		if err != nil {
			return nil, err
		}
	} else {
		zapLogger, err = zap.NewDevelopment(zapOptions...)
		if err != nil {
			return nil, err
		}
	}

	zapLogger = zapLogger.With(
		zap.String("SvcCommit", ver.Commit),
		zap.String("SvcVersion", ver.Release),
		zap.String("SvcBuildTime", ver.BuildTime),
	)

	logger := hlog.New(zapLogger)
	logger.Emit(InfoSvcStarted{})
	zapLogger.Info("config", zap.Any("options", options))

	go func() {
		for {
			select {
			case <-ctx.Done():
				return
			case err := <-errChan:
				zapLogger.Error("Ошибка при работе воркера", zap.Error(err))
			}
		}
	}()

	//init redis
	redisClient := redis.NewClient(&redis.Options{
		Addr:     options.RedisHost,
		Password: options.RedisPassword,
		DB:       int(options.RedisDB),
	})

	mailClent := clients.NewSmtpClient(clients.Deps{
		SmtpHost:   options.SmtpHost,
		SmtpPort:   options.SmtpPort,
		SmtpSender: options.SmtpSender,
		ApiKey:     options.SmtpApiKey,
		SmtpApiUrl: options.SmtpApiUrl,
	})

	tgSender, err := senders.NewTgSender(options.TgToken)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	mailSender := senders.NewMailLeadSender(mailClent)
	leadSenders := []senders.LeadSender{mailSender, tgSender}

	customerClient := customer_clients.NewCustomersClient(customer_clients.CustomersClientDeps{
		Logger:              zapLogger,
		CustomerServiceHost: options.CustomerServiceAddress,
	})

	minioClient, err := minio.New(options.MinioEP, &minio.Options{
		Creds:  credentials.NewStaticV4(options.MinioAK, options.MinioSK, ""),
		Secure: options.IsProd,
	})
	if err != nil {
		fmt.Println("MINIOERR", options.MinioEP, err)
		return nil, err
	}

	pgdal, err := dal.New(ctx, options.PostgresCredentials, minioClient)
	if err != nil {
		return nil, err
	}

	kafkaWorker, err := privilegewc.NewKafkaConsumerWorker(privilegewc.Config{
		KafkaBroker:    options.KafkaBroker,
		KafkaTopic:     options.KafkaTopic,
		ServiceKey:     options.ServiceName,
		TickerInterval: time.Second * 10,
		ErrChan:        errChan,
	}, redisClient, pgdal)
	if err != nil {
		logger.Module("Failed start privilege worker")
		return nil, err
	}

	checkWorker := privilegewc.NewCheckWorker(privilegewc.Deps{
		PrivilegeIDsDays:  []string{"quizUnlimTime", "squizHideBadge"},
		PrivilegeIDsCount: []string{"quizCnt", "quizManual"},
		TickerInterval:    time.Minute,
		PrivilegeDAL:      pgdal,
		CustomerClient:    customerClient,
	}, errChan)

	go kafkaWorker.Start(ctx)
	go checkWorker.Start(ctx)

	toClientWorker := answerwc.NewSendToClient(answerwc.DepsSendToClient{
		Redis:           redisClient,
		Dal:             pgdal,
		LeadSenders:     leadSenders,
		CustomerService: customerClient,
	}, errChan)

	toRespWorker := answerwc.NewRespWorker(answerwc.DepsRespWorker{
		Redis:      redisClient,
		Dal:        pgdal,
		MailClient: mailSender,
	}, errChan)

	go toClientWorker.Start(ctx)
	go toRespWorker.Start(ctx)

	tow := timeout.New(pgdal, time.Minute)
	statW := shortstat.New(pgdal, 5*time.Minute)
	tow.ExposeErr(ctx, &workerErr)
	statW.ExposeErr(ctx, &workerErr)
	go tow.Start(ctx)
	go func() {
		//		defer pgdal.CloseWorker()
		statW.Start(ctx)
	}()

	logger.Emit(InfoSvcReady{})
	// todo implement helper func for service app type. such as server preparing, logger preparing, healthchecks and etc.
	return &App{
		logger: zapLogger,
		err:    make(chan error),
	}, err
}
