package models

import (
	"context"
	"errors"
	"github.com/google/uuid"
	"github.com/open-policy-agent/opa/v1/rego"
)

type JWT struct {
	SessionId   string  `json:"session_id"`
	UserId      int32   `json:"user_id"`
	Role        Role    `json:"role"`
	ExpiresAt   int64   `json:"exp"`
	IssuedAt    int64   `json:"iat"`
	NotBefore   int64   `json:"nbf"`
	Permissions []grant `json:"permissions"`
}

func (j JWT) Valid() error {
	if uuid.Validate(j.SessionId) != nil {
		return errors.New("invalid session id")
	}
	if j.UserId == 0 {
		return errors.New("empty user id")
	}
	if j.ExpiresAt == 0 {
		return errors.New("empty expires at")
	}
	if j.IssuedAt == 0 {
		return errors.New("empty issued at")
	}
	if j.NotBefore == 0 {
		return errors.New("empty not before")
	}
	if len(j.Permissions) == 0 {
		return errors.New("empty permissions")
	}
	return nil
}

type Role int32

const (
	RoleGuest   Role = -1
	RoleStudent Role = 0
	RoleTeacher Role = 1
	RoleAdmin   Role = 2
)

func (r Role) String() string {
	switch r {
	case RoleGuest:
		return "guest"
	case RoleStudent:
		return "student"
	case RoleTeacher:
		return "teacher"
	case RoleAdmin:
		return "admin"
	}

	panic("invalid role")
}

type Action string

const (
	Create Action = "create"
	Read   Action = "read"
	Update Action = "update"
	Delete Action = "delete"
)

type Resource string

const (
	ResourceAnotherUser Resource = "another-user"
	ResourceMeUser      Resource = "me-user"
	ResourceListUser    Resource = "list-user"

	ResourceOwnSession Resource = "own-session"
)

type grant struct {
	Action   Action   `json:"action"`
	Resource Resource `json:"resource"`
}

var Grants = map[string][]grant{
	RoleGuest.String(): {},
	RoleStudent.String(): {
		{Read, ResourceAnotherUser},
		{Read, ResourceMeUser},
		{Update, ResourceOwnSession},
		{Delete, ResourceOwnSession},
	},
	RoleTeacher.String(): {
		{Create, ResourceAnotherUser},
		{Read, ResourceAnotherUser},
		{Read, ResourceMeUser},
		{Read, ResourceListUser},
		{Update, ResourceOwnSession},
		{Delete, ResourceOwnSession},
	},
	RoleAdmin.String(): {
		{Create, ResourceAnotherUser},
		{Read, ResourceAnotherUser},
		{Read, ResourceMeUser},
		{Read, ResourceListUser},
		{Update, ResourceAnotherUser},
		{Update, ResourceOwnSession},
		{Delete, ResourceAnotherUser},
		{Delete, ResourceOwnSession},
	},
}

const module = `package app.rbac

default allow := false

allow if {
  some grant in input.role_grants[input.role]

  input.action == grant.action
  input.resource == grant.resource
}
`

var query rego.PreparedEvalQuery

func (r Role) HasPermission(action Action, resource Resource) bool {
	ctx := context.TODO()

	input := map[string]interface{}{
		"action":      action,
		"resource":    resource,
		"role":        r.String(),
		"role_grants": Grants,
	}

	results, err := query.Eval(ctx, rego.EvalInput(input))
	if err != nil {
		panic(err)
	}

	return results.Allowed()
}

func init() {
	var err error
	ctx := context.TODO()

	query, err = rego.New(
		rego.Query("data.app.rbac.allow"),
		rego.Module("ms-auth.rego", module),
	).PrepareForEval(ctx)

	if err != nil {
		panic(err)
	}
}