package bootstrap

import (
	"fmt"
	"os"

	"github.com/tinyauthapp/tinyauth/internal/service"
	"go.uber.org/dig"
)

func (app *BootstrapApp) setupServices() error {
	err := app.setupPolicyEngine()

	if err != nil {
		return fmt.Errorf("failed to setup policy engine: %w", err)
	}

	labelProvider, err := app.getLabelProvider()

	if err != nil {
		return fmt.Errorf("failed to get label provider: %w", err)
	}

	serviceProvideFor := []any{
		func() service.LabelProvider {
			return labelProvider
		},
		service.NewLdapService,
		service.NewTailscaleService,
		service.NewAccessControlsService,
		service.NewOAuthBrokerService,
		service.NewAuthService,
		service.NewOIDCService,
	}

	for _, provider := range serviceProvideFor {
		err = app.dig.Provide(provider)

		if err != nil {
			return fmt.Errorf("failed to provide service: %w", err)
		}
	}

	type svcInput struct {
		dig.In

		AccessControlService *service.AccessControlsService
		AuthService          *service.AuthService
		LDAPService          *service.LdapService
		OAuthBrokerService   *service.OAuthBrokerService
		OIDCService          *service.OIDCService
		TailscaleService     *service.TailscaleService
	}

	err = app.dig.Invoke(func(i svcInput) error {
		app.services.accessControlService = i.AccessControlService
		app.services.authService = i.AuthService
		app.services.ldapService = i.LDAPService
		app.services.oauthBrokerService = i.OAuthBrokerService
		app.services.oidcService = i.OIDCService
		app.services.tailscaleService = i.TailscaleService
		return nil
	})

	if err != nil {
		return fmt.Errorf("failed to invoke services: %w", err)
	}

	return nil
}

func (app *BootstrapApp) getLabelProvider() (service.LabelProvider, error) {
	switch app.config.LabelProvider {
	case "none", "docker", "kubernetes", "auto":
		if app.config.LabelProvider == "none" {
			return nil, nil
		}

		useKubernetes := app.config.LabelProvider == "kubernetes" ||
			(app.config.LabelProvider == "auto" && os.Getenv("KUBERNETES_SERVICE_HOST") != "")

		if useKubernetes {
			app.log.App.Debug().Msg("Using Kubernetes label provider")

			err := app.dig.Provide(service.NewKubernetesService)

			if err != nil {
				app.log.App.Warn().Err(err).Msg("Failed to provide kubernetes service")
				return nil, nil
			}

			err = app.dig.Invoke(func(k *service.KubernetesService) error {
				app.services.kubernetesService = k
				return nil
			})

			if err != nil {
				app.log.App.Warn().Err(err).Msg("Failed to invoke kubernetes service")
				return nil, nil
			}

			// Kubernetes will fail to initialize with an error if it cannot connect to the cluster
			// but just to be safe, we check if the service is nil and log a warning if it is
			if app.services.kubernetesService == nil {
				if app.config.LabelProvider == "kubernetes" {
					app.log.App.Warn().Msg("Kubernetes label provider selected but Kubernetes is not available, will continue without it")
				}
				return nil, nil
			}

			return app.services.kubernetesService, nil
		}

		app.log.App.Debug().Msg("Using Docker label provider")

		err := app.dig.Provide(service.NewDockerService)

		if err != nil {
			app.log.App.Warn().Err(err).Msg("Failed to provide docker service")
			return nil, nil
		}

		err = app.dig.Invoke(func(d *service.DockerService) error {
			app.services.dockerService = d
			return nil
		})

		if err != nil {
			app.log.App.Warn().Err(err).Msg("Failed to invoke docker service")
			return nil, nil
		}

		if app.services.dockerService == nil {
			if app.config.LabelProvider == "docker" {
				app.log.App.Warn().Msg("Docker label provider selected but Docker is not available, will continue without it")
			}
			return nil, nil
		}

		return app.services.dockerService, nil
	default:
		return nil, fmt.Errorf("invalid label provider: %s", app.config.LabelProvider)
	}
}

func (app *BootstrapApp) setupPolicyEngine() error {
	err := app.dig.Provide(service.NewPolicyEngine)

	if err != nil {
		return fmt.Errorf("failed to create policy engine: %w", err)
	}

	err = app.dig.Invoke(func(policyEngine *service.PolicyEngine) error {
		policyEngine.RegisterRule(service.RuleUserAllowed, &service.UserAllowedRule{
			Log: app.log,
		})
		policyEngine.RegisterRule(service.RuleOAuthGroup, &service.OAuthGroupRule{
			Log: app.log,
		})
		policyEngine.RegisterRule(service.RuleLDAPGroup, &service.LDAPGroupRule{
			Log: app.log,
		})
		policyEngine.RegisterRule(service.RuleAuthEnabled, &service.AuthEnabledRule{
			Log: app.log,
		})
		policyEngine.RegisterRule(service.RuleIPAllowed, &service.IPAllowedRule{
			Log:    app.log,
			Config: app.config,
		})
		policyEngine.RegisterRule(service.RuleIPBypassed, &service.IPBypassedRule{
			Log:    app.log,
			Config: app.config,
		})
		return nil
	})

	return err
}
