package schema

import (
	_ "embed"

	"github.com/santhosh-tekuri/jsonschema/v5"

	"github.com/zitadel/zitadel/internal/zerrors"
)

var (
	//go:embed permission.schema.v1.json
	permissionJSON string

	permissionSchema = jsonschema.MustCompileString(PermissionSchemaID, permissionJSON)
)

const (
	PermissionSchemaID = "urn:zitadel:schema:permission-schema:v1"
	PermissionProperty = "urn:zitadel:schema:permission"
)

type role int32

const (
	roleUnspecified role = iota
	roleSelf
	roleOwner
)

type permissionExtension struct {
	role role
}

// Compile implements the [jsonschema.ExtCompiler] interface.
// It parses the permission schema extension / annotation of the passed field.
func (c permissionExtension) Compile(ctx jsonschema.CompilerContext, m map[string]interface{}) (_ jsonschema.ExtSchema, err error) {
	perm, ok := m[PermissionProperty]
	if !ok {
		return nil, nil
	}
	p, ok := perm.(map[string]interface{})
	if !ok {
		return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-WR5gs", "invalid permission")
	}
	perms := new(permissions)
	for key, value := range p {
		switch key {
		case "self":
			perms.self, err = mapPermission(value)
			if err != nil {
				return
			}
		case "owner":
			perms.owner, err = mapPermission(value)
			if err != nil {
				return
			}
		default:
			return nil, zerrors.ThrowInvalidArgument(nil, "SCHEMA-GFjio", "invalid permission role")
		}
	}
	return permissionExtensionConfig{c.role, perms}, nil
}

type permissionExtensionConfig struct {
	role        role
	permissions *permissions
}

// Validate implements the [jsonschema.ExtSchema] interface.
// It validates the fields of the json instance according to the permission schema.
func (s permissionExtensionConfig) Validate(ctx jsonschema.ValidationContext, v interface{}) error {
	switch s.role {
	case roleSelf:
		if s.permissions.self == nil || !s.permissions.self.write {
			return ctx.Error("permission", "missing required permission")
		}
		return nil
	case roleOwner:
		if s.permissions.owner == nil || !s.permissions.owner.write {
			return ctx.Error("permission", "missing required permission")
		}
		return nil
	case roleUnspecified:
		fallthrough
	default:
		return ctx.Error("permission", "missing required permission")
	}
}

func mapPermission(value any) (*permission, error) {
	p := new(permission)
	switch v := value.(type) {
	case string:
		for _, s := range v {
			switch s {
			case 'r':
				p.read = true
			case 'w':
				p.write = true
			default:
				return nil, zerrors.ThrowInvalidArgumentf(nil, "SCHEMA-EZ5zjh", "invalid permission pattern: `%s` in (%s)", string(s), v)
			}
		}
		return p, nil
	default:
		return nil, zerrors.ThrowInvalidArgumentf(nil, "SCHEMA-E5h31", "invalid permission type %T (%v)", v, v)
	}
}

type permissions struct {
	self  *permission
	owner *permission
}

type permission struct {
	read  bool
	write bool
}