package main

import (
	"context"
	"errors"
	"fmt"
	"oc-auth/conf"
	"oc-auth/infrastructure"
	auth_connectors "oc-auth/infrastructure/auth_connector"
	_ "oc-auth/routers"
	"os"
	"strconv"
	"strings"

	oclib "cloud.o-forge.io/core/oc-lib"
	peer "cloud.o-forge.io/core/oc-lib/models/peer"
	"cloud.o-forge.io/core/oc-lib/models/utils"
	"cloud.o-forge.io/core/oc-lib/tools"
	beego "github.com/beego/beego/v2/server/web"
	"github.com/beego/beego/v2/server/web/filter/cors"
)

const appname = "oc-auth"

// @securityDefinitions.apikey Bearer
// @in header
// @name Authorization
// @description Type "Bearer" followed by a space and JWT token.
func main() {
	// Init the oc-lib
	oclib.Init(appname)
	// Load the right config file
	o := oclib.GetConfLoader()

	conf.GetConfig().AdminRole = o.GetStringDefault("ADMIN_ROLE", "admin")
	conf.GetConfig().PublicKeyPath = o.GetStringDefault("PUBLIC_KEY_PATH", "./pem/public.pem")
	conf.GetConfig().PrivateKeyPath = o.GetStringDefault("PRIVATE_KEY_PATH", "./pem/private.pem")
	conf.GetConfig().ClientSecret = o.GetStringDefault("CLIENT_SECRET", "oc-auth-got-secret")
	conf.GetConfig().OAuth2ClientSecretName = o.GetStringDefault("OAUTH2_CLIENT_SECRET_NAME", "oc-oauth2-client-secret")
	conf.GetConfig().OAuth2ClientSecretNamespace = o.GetStringDefault("NAMESPACE", "default")
	conf.GetConfig().Auth = o.GetStringDefault("AUTH", "hydra")
	conf.GetConfig().AuthConnectorHost = o.GetStringDefault("AUTH_CONNECTOR_HOST", "localhost")
	conf.GetConfig().AuthConnectPublicHost = o.GetStringDefault("AUTH_CONNECTOR_PUBLIC_HOST", "localhost")
	conf.GetConfig().AuthConnectorPort = o.GetIntDefault("AUTH_CONNECTOR_PORT", 4444)
	conf.GetConfig().AuthConnectorAdminPort = o.GetIntDefault("AUTH_CONNECTOR_ADMIN_PORT", 4445)
	conf.GetConfig().PermissionConnectorHost = o.GetStringDefault("PERMISSION_CONNECTOR_HOST", "keto")
	conf.GetConfig().PermissionConnectorPort = o.GetIntDefault("PERMISSION_CONNECTOR_PORT", 4466)
	conf.GetConfig().PermissionConnectorAdminPort = o.GetIntDefault("PERMISSION_CONNECTOR_ADMIN_PORT", 4467)

	// config LDAP
	conf.GetConfig().SourceMode = o.GetStringDefault("SOURCE_MODE", "ldap")
	conf.GetConfig().LDAPEndpoints = o.GetStringDefault("LDAP_ENDPOINTS", "ldap:389")
	conf.GetConfig().LDAPBindDN = o.GetStringDefault("LDAP_BINDDN", "cn=admin,dc=example,dc=com")
	conf.GetConfig().LDAPBindPW = o.GetStringDefault("LDAP_BINDPW", "password")
	conf.GetConfig().LDAPBaseDN = o.GetStringDefault("LDAP_BASEDN", "dc=example,dc=com")
	conf.GetConfig().LDAPRoleBaseDN = o.GetStringDefault("LDAP_ROLE_BASEDN", "ou=AppRoles,dc=example,dc=com")
	err := generateSelfPeer()
	if err != nil {
		panic(err)
	}
	generateRole()
	discovery()
	beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
		AllowAllOrigins:  true,
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Authorization", "Content-Type"},
		ExposeHeaders:    []string{"Content-Length", "Content-Type"},
		AllowCredentials: true,
	}))
	beego.Run()
}

func generateRole() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()
	// if from ldap, create roles from ldap
	if conf.GetConfig().SourceMode == "ldap" {
		ldap := auth_connectors.New()
		roles, err := ldap.GetRoles(context.Background())
		if err != nil {
			panic(err)
		}
		fmt.Println("ROLE", roles)
		for _, role := range roles {
			for r, m := range role.Members {
				infrastructure.GetPermissionConnector("").CreateRole(r)
				for _, p := range m {
					infrastructure.GetPermissionConnector("").BindRole(r, p)
				}
			}
		}
	}
}

func generateSelfPeer() error {
	// TODO check if files at private & public path are set
	// check if files at private & public path are set
	if _, err := os.Stat(conf.GetConfig().PrivateKeyPath); errors.Is(err, os.ErrNotExist) {
		return errors.New("private key path does not exist")
	}
	if _, err := os.Stat(conf.GetConfig().PublicKeyPath); errors.Is(err, os.ErrNotExist) {
		return errors.New("public key path does not exist")
	}
	// check if peer already exists
	p := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil).Search(nil, strconv.Itoa(peer.SELF.EnumIndex()), false)
	file := ""
	f, err := os.ReadFile(conf.GetConfig().PublicKeyPath)
	if err != nil {
		return err
	}
	file = string(f)
	if len(p.Data) > 0 {
		// check public key with the one in the database
		// compare the public key from file with the one in the database
		if !strings.Contains(file, p.Data[0].(*peer.Peer).PublicKey) {
			return errors.New("public key is different from the one in the database")
		}
		return nil
	}
	// create a new peer
	o := oclib.GetConfLoader()
	peer := &peer.Peer{
		Url: o.GetStringDefault("HOSTNAME", "http://localhost"),
		AbstractObject: utils.AbstractObject{
			Name: o.GetStringDefault("NAME", "local"),
		},
		PublicKey: file,
		State:     peer.SELF,
	}
	data := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil).StoreOne(peer.Serialize(peer))
	if data.Err != "" {
		return errors.New(data.Err)
	}
	return nil
}

func discovery() {
	api := tools.API{}
	conn := infrastructure.GetPermissionConnector("")

	conn.CreateRole(conf.GetConfig().AdminRole)
	conn.BindRole(conf.GetConfig().AdminRole, "admin")
	addPermissions := func(m map[string]interface{}) {
		for k, v := range m {
			for _, p := range v.([]interface{}) {
				conn.CreatePermission(k, p.(string), true)
			}
		}
	}
	api.ListenRouter(addPermissions)
	tools.NewNATSCaller().SetNATSPub("api", tools.DISCOVERY, map[string]interface{}{})
}
