package controllers

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"oc-auth/conf"
	"oc-auth/infrastructure"
	auth_connectors "oc-auth/infrastructure/auth_connector"
	"regexp"
	"strings"

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

// Operations about auth
type OAuthController struct {
	beego.Controller
}

// @Title Logout
// @Description unauthenticate user
// @Param	Authorization		header  string	false "auth token"
// @Param   client_id   query    string  true        "the client_id you want to get"
// @Success 200 {string}
// @router /logout [delete]
func (o *OAuthController) LogOut() {
	// authorize user
	clientID := o.Ctx.Input.Query("client_id")
	reqToken := o.Ctx.Request.Header.Get("Authorization")
	splitToken := strings.Split(reqToken, "Bearer ")
	if len(splitToken) < 2 {
		reqToken = ""
	} else {
		reqToken = splitToken[1]
	}
	var res auth_connectors.Token
	json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &res)

	token, err := infrastructure.GetAuthConnector().Logout(clientID, reqToken)
	if err != nil || token == nil {
		o.Data["json"] = err
	} else {
		o.Data["json"] = token
	}
	o.ServeJSON()
}

// @Title Login
// @Description authenticate user
// @Param	body		body 	models.workflow	true		"The workflow content"
// @Param   client_id   query    string  true        "the client_id you want to get"
// @Success 200 {string}
// @router /login [post]
func (o *OAuthController) Login() {
	// authorize user
	fmt.Println("Login", o.Ctx.Input.Query("client_id"), o.Ctx.Input.Param(":client_id"))
	clientID := o.Ctx.Input.Query("client_id")
	var res auth_connectors.Token
	json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &res)
	if conf.GetConfig().SourceMode == "ldap" {
		ldap := auth_connectors.New()
		found, err := ldap.Authenticate(o.Ctx.Request.Context(), res.Username, res.Password)
		fmt.Println("found", found, "err", err)
		if err != nil || !found {
			o.Data["json"] = err
			o.Ctx.ResponseWriter.WriteHeader(401)
			o.ServeJSON()
			return
		}
	}
	token, err := infrastructure.GetAuthConnector().Login(
		clientID, res.Username,
		&http.Cookie{ // open a session
			Name:  "csrf_token",
			Value: o.XSRFToken(),
		})
	fmt.Println("token", token, "err", err)
	if err != nil || token == nil {
		o.Data["json"] = err
		o.Ctx.ResponseWriter.WriteHeader(401)
	} else {
		o.Data["json"] = token
	}
	o.ServeJSON()
}

// @Title Introspection
// @Description introspect token
// @Param	body		body 	models.Token	true		"The token info"
// @Param   client_id   query    string  true        "the client_id you want to get"
// @Success 200 {string}
// @router /refresh [post]
func (o *OAuthController) Refresh() {
	clientID := o.Ctx.Input.Query("client_id")
	var token auth_connectors.Token
	json.Unmarshal(o.Ctx.Input.CopyBody(100000), &token)
	// refresh token
	newToken, err := infrastructure.GetAuthConnector().Refresh(clientID, &token)
	if err != nil || newToken == nil {
		o.Data["json"] = err
		o.Ctx.ResponseWriter.WriteHeader(401)
	} else {
		o.Data["json"] = newToken
	}
	o.ServeJSON()
}

// @Title Introspection
// @Description introspect token
// @Param	Authorization		header  string	false "auth token"
// @Success 200 {string}
// @router /introspect [get]
func (o *OAuthController) Introspect() {
	reqToken := o.Ctx.Request.Header.Get("Authorization")
	splitToken := strings.Split(reqToken, "Bearer ")
	if len(splitToken) < 2 {
		reqToken = ""
	} else {
		reqToken = splitToken[1]
	}

	token, err := infrastructure.GetAuthConnector().Introspect(reqToken)
	if err != nil || !token {
		o.Data["json"] = err
		o.Ctx.ResponseWriter.WriteHeader(401)
	}
	o.ServeJSON()
}

var whitelist = []string{
	"/login",
	"/refresh",
	"/introspect",
}

// @Title AuthForward
// @Description auth forward
// @Param	Authorization		header  string	false "auth token"
// @Success 200 {string}
// @router /forward [get]
func (o *OAuthController) InternaisDraftlAuthForward() {
	fmt.Println("InternalAuthForward")
	reqToken := o.Ctx.Request.Header.Get("Authorization")
	if reqToken == "" {
		for _, w := range whitelist {
			if strings.Contains(o.Ctx.Request.Header.Get("X-Forwarded-Uri"), w) {
				o.Ctx.ResponseWriter.WriteHeader(200)
				o.ServeJSON()
				return
			}
		}
		o.Ctx.ResponseWriter.WriteHeader(401)
		o.ServeJSON()
		return
	}
	splitToken := strings.Split(reqToken, "Bearer ")
	if len(splitToken) < 2 {
		reqToken = ""
	} else {
		reqToken = splitToken[1]
	}
	origin, publicKey, external := o.extractOrigin(o.Ctx.Request)
	if !infrastructure.GetAuthConnector().CheckAuthForward( //reqToken != "" &&
		reqToken, publicKey, origin,
		o.Ctx.Request.Header.Get("X-Forwarded-Method"),
		o.Ctx.Request.Header.Get("X-Forwarded-Uri"), external) && origin != "" && publicKey != "" {
		o.Ctx.ResponseWriter.WriteHeader(401)
		o.ServeJSON()
		return
	}
	o.ServeJSON()
}

func (o *OAuthController) extractOrigin(request *http.Request) (string, string, bool) {
	user, peerID, groups := oclib.ExtractTokenInfo(*request)
	external := true
	publicKey := ""
	origin := o.Ctx.Request.Header.Get("X-Forwarded-Host")
	if origin == "" {
		origin = o.Ctx.Request.Header.Get("Origin")
	}
	searchStr := origin
	r := regexp.MustCompile("(:[0-9]+)")
	t := r.FindString(searchStr)
	if t != "" {
		searchStr = strings.Replace(searchStr, t, "", -1)
	}
	peer := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), user, peerID, groups, nil).Search(nil, searchStr, false)
	if peer.Code != 200 || len(peer.Data) == 0 { // TODO: add state of partnership
		return "", "", external
	}
	p := peer.Data[0].(*model.Peer)
	publicKey = p.PublicKey
	origin = p.Url
	if origin != "" { // is external
		if strings.Contains(origin, "localhost") || strings.Contains(origin, "127.0.0.1") || p.State == model.SELF {
			external = false
		}
	} else {
		external = false
	}
	return origin, publicKey, external
}

func ExtractClient(request http.Request) string {
	reqToken := request.Header.Get("Authorization")
	splitToken := strings.Split(reqToken, "Bearer ")
	if len(splitToken) < 2 {
		reqToken = ""
	} else {
		reqToken = splitToken[1]
	}
	if reqToken != "" {
		token := strings.Split(reqToken, ".")
		if len(token) > 2 {
			bytes, err := base64.StdEncoding.DecodeString(token[2])
			if err != nil {
				return ""
			}
			m := map[string]interface{}{}
			err = json.Unmarshal(bytes, &m)
			if err != nil {
				return ""
			}
			return m["session"].(map[string]interface{})["id_token"].(map[string]interface{})["client_id"].(string)
		}
	}
	return ""
}
