package apiimp

import (
	"context"

	"github.com/go-chi/chi/v5"
	"github.com/tech/sendico/pkg/api/routers/health"
	"github.com/tech/sendico/pkg/auth"
	"github.com/tech/sendico/pkg/db"
	"github.com/tech/sendico/pkg/domainprovider"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mservice"
	"github.com/tech/sendico/server/interface/api"
	"github.com/tech/sendico/server/interface/services/account"
	"github.com/tech/sendico/server/interface/services/callbacks"
	"github.com/tech/sendico/server/interface/services/invitation"
	"github.com/tech/sendico/server/interface/services/ledger"
	"github.com/tech/sendico/server/interface/services/logo"
	"github.com/tech/sendico/server/interface/services/organization"
	"github.com/tech/sendico/server/interface/services/payment"
	"github.com/tech/sendico/server/interface/services/paymethod"
	"github.com/tech/sendico/server/interface/services/permission"
	"github.com/tech/sendico/server/interface/services/recipient"
	"github.com/tech/sendico/server/interface/services/site"
	"github.com/tech/sendico/server/interface/services/verification"
	"github.com/tech/sendico/server/interface/services/wallet"
	"go.uber.org/zap"
)

type Microservices = []mservice.MicroService

// APIImp represents the structure of the APIImp
type APIImp struct {
	logger   mlogger.Logger
	db       db.Factory
	domain   domainprovider.DomainProvider
	config   *api.Config
	services Microservices
	mw       *Middleware
}

func (a *APIImp) installMicroservice(srv mservice.MicroService) {
	a.services = append(a.services, srv)
	a.logger.Info("Microservice installed", zap.String("service", srv.Name()))
}

func (a *APIImp) addMicroservice(srvf api.MicroServiceFactoryT) error {
	srv, err := srvf(a)
	if err != nil {
		a.logger.Error("Failed to install a microservice", zap.Error(err))
		return err
	}

	a.installMicroservice(srv)
	return nil
}

func (a *APIImp) Logger() mlogger.Logger {
	return a.logger
}

func (a *APIImp) Config() *api.Config {
	return a.config
}

func (a *APIImp) DBFactory() db.Factory {
	return a.db
}

func (a *APIImp) DomainProvider() domainprovider.DomainProvider {
	return a.domain
}

func (a *APIImp) Register() api.Register {
	return a.mw
}

func (a *APIImp) Permissions() auth.Provider {
	return a.db.Permissions()
}

func (a *APIImp) installServices() error {
	srvf := make([]api.MicroServiceFactoryT, 0)

	srvf = append(srvf, account.Create)
	srvf = append(srvf, verification.Create)
	srvf = append(srvf, organization.Create)
	srvf = append(srvf, invitation.Create)
	srvf = append(srvf, logo.Create)
	srvf = append(srvf, permission.Create)
	srvf = append(srvf, site.Create)
	srvf = append(srvf, wallet.Create)
	srvf = append(srvf, ledger.Create)
	srvf = append(srvf, recipient.Create)
	srvf = append(srvf, callbacks.Create)
	srvf = append(srvf, paymethod.Create)
	srvf = append(srvf, payment.Create)

	for _, v := range srvf {
		if err := a.addMicroservice(v); err != nil {
			return err
		}
	}

	a.mw.SetStatus(health.SSRunning)
	return nil
}

func (a *APIImp) Finish(ctx context.Context) error {
	a.mw.SetStatus(health.SSTerminating)
	a.mw.Finish()
	var lastError error
	for i := len(a.services) - 1; i >= 0; i-- {
		if err := (a.services[i]).Finish(ctx); err != nil {
			lastError = err
			a.logger.Warn("Error occurred when finishing service",
				zap.Error(err), zap.String("service_name", (a.services[i]).Name()))
		} else {
			a.logger.Info("Microservice is down", zap.String("service_name", (a.services[i]).Name()))
		}
	}
	return lastError
}

func (a *APIImp) Name() string {
	return "api"
}

func CreateAPI(logger mlogger.Logger, config *api.Config, db db.Factory, router *chi.Mux, debug bool) (mservice.MicroService, error) {
	p := &APIImp{
		logger: logger.Named("api"),
		config: config,
		db:     db,
	}

	var err error
	if p.domain, err = domainprovider.CreateDomainProvider(p.logger, config.Mw.DomainEnv, config.Mw.APIProtocolEnv, config.Mw.EndPointEnv); err != nil {
		p.logger.Error("Failed to initizlize domain provider")
		return nil, err
	}
	p.logger.Info("Domain provider installed")

	if p.mw, err = CreateMiddleware(logger, db, p.db.Permissions().Enforcer(), router, config.Mw, debug); err != nil {
		p.logger.Error("Failed to create middleware", zap.Error(err))
		return nil, err
	}
	p.logger.Info("Middleware installed", zap.Bool("debug_mode", debug))

	p.resolveServiceAddressesFromDiscovery()

	p.logger.Info("Installing microservices...")
	if err := p.installServices(); err != nil {
		p.logger.Error("Failed to install a microservice", zap.Error(err))
		return nil, err
	}
	p.logger.Info("Microservices installation complete", zap.Int("microservices", len(p.services)))

	return p, nil
}
