package permission

import "strings"

// Role is a platform-level role carried in OIDC groups.
type Role string

const (
	RoleAdmin   Role = "admin"
	RoleUser    Role = "user"
	RoleGuest   Role = "guest"
	RoleService Role = "service"
)

// AccountRole is the admin-facing lifecycle role stored as status + platform_admin.
type AccountRole string

const (
	AccountRoleAdmin     AccountRole = "admin"
	AccountRoleUser      AccountRole = "user"
	AccountRoleGuest     AccountRole = "guest"
	AccountRoleSuspended AccountRole = "suspended"
)

// DeriveAccountRole maps database fields to the admin UI role.
func DeriveAccountRole(platformAdmin bool, status string) AccountRole {
	switch strings.ToLower(strings.TrimSpace(status)) {
	case "disabled":
		return AccountRoleSuspended
	case "invited":
		return AccountRoleGuest
	default:
		if platformAdmin {
			return AccountRoleAdmin
		}
		return AccountRoleUser
	}
}

// ParseAccountRole validates an admin role string.
func ParseAccountRole(raw string) (AccountRole, bool) {
	switch AccountRole(strings.ToLower(strings.TrimSpace(raw))) {
	case AccountRoleAdmin, AccountRoleUser, AccountRoleGuest, AccountRoleSuspended:
		return AccountRole(strings.ToLower(strings.TrimSpace(raw))), true
	default:
		return "", false
	}
}

// Resource is a suite module protected by resource-scoped permissions.
type Resource string

const (
	ResourceDrive    Resource = "drive"
	ResourcePhotos   Resource = "photos"
	ResourceContacts Resource = "contacts"
	ResourceCalendar Resource = "calendar"
)

// Level is a resource permission with read < write < admin ordering.
type Level int

const (
	LevelRead Level = iota + 1
	LevelWrite
	LevelAdmin
)

func (l Level) String() string {
	switch l {
	case LevelRead:
		return "read"
	case LevelWrite:
		return "write"
	case LevelAdmin:
		return "admin"
	default:
		return "unknown"
	}
}

func ParseLevel(s string) (Level, bool) {
	switch strings.ToLower(strings.TrimSpace(s)) {
	case "read":
		return LevelRead, true
	case "write":
		return LevelWrite, true
	case "admin":
		return LevelAdmin, true
	default:
		return 0, false
	}
}

func levelRank(l Level) int {
	return int(l)
}

var suiteResources = []Resource{
	ResourceContacts,
	ResourceCalendar,
	ResourceDrive,
	ResourcePhotos,
}

func hasAnyResourcePermission(groups []string) bool {
	for _, g := range groups {
		g = strings.ToLower(strings.TrimSpace(g))
		for _, resource := range suiteResources {
			if strings.HasPrefix(g, string(resource)+":") {
				return true
			}
		}
	}
	return false
}

// WithSuiteDefaults grants standard suite read/write when the token carries no
// resource-scoped groups. Mail endpoints stay open; CardDAV/CalDAV modules rely
// on this until Authentik emits explicit RBAC groups on every account.
func WithSuiteDefaults(groups []string) []string {
	if hasAnyResourcePermission(groups) {
		return groups
	}

	defaults := []string{
		string(RoleUser),
		string(ResourceContacts) + ":write",
		string(ResourceCalendar) + ":write",
		string(ResourceDrive) + ":write",
		string(ResourcePhotos) + ":write",
	}

	seen := make(map[string]struct{}, len(groups)+len(defaults))
	out := make([]string, 0, len(groups)+len(defaults))
	for _, g := range groups {
		g = strings.TrimSpace(g)
		if g == "" {
			continue
		}
		key := strings.ToLower(g)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, g)
	}
	for _, g := range defaults {
		key := strings.ToLower(g)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, g)
	}
	return out
}

// WithGuestAccess limits suite access to drive (shared + own files via Nextcloud).
// Guests must not receive suite-wide defaults (mail, contacts, calendar, photos).
func WithGuestAccess(groups []string) []string {
	guestGroups := []string{
		string(RoleGuest),
		string(ResourceDrive) + ":write",
	}

	seen := make(map[string]struct{}, len(groups)+len(guestGroups))
	out := make([]string, 0, len(guestGroups))
	for _, g := range guestGroups {
		key := strings.ToLower(strings.TrimSpace(g))
		if key == "" {
			continue
		}
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, g)
	}
	return out
}

// WithPlatformAdmin grants full platform admin groups when missing.
func WithPlatformAdmin(groups []string) []string {
	if HasRole(groups, RoleAdmin) && HasAdminScope(groups, AdminScopeWrite) {
		return groups
	}
	out := append([]string{}, groups...)
	if !HasRole(groups, RoleAdmin) {
		out = append(out, string(RoleAdmin))
	}
	if !HasAdminScope(groups, AdminScopeWrite) {
		out = append(out, GroupAdminWrite)
	}
	return out
}

// AdminScope is a fine-grained admin API permission with read < write ordering.
type AdminScope int

const (
	AdminScopeRead AdminScope = iota + 1
	AdminScopeWrite
)

// DefaultAdminScope is the scope assumed when an endpoint requires full admin API access.
const DefaultAdminScope = AdminScopeWrite

const (
	GroupAdminRead  = "admin:read"
	GroupAdminWrite = "admin:write"
)

func (s AdminScope) String() string {
	switch s {
	case AdminScopeRead:
		return "read"
	case AdminScopeWrite:
		return "write"
	default:
		return "unknown"
	}
}

func ParseAdminScope(s string) (AdminScope, bool) {
	switch strings.ToLower(strings.TrimSpace(s)) {
	case "read":
		return AdminScopeRead, true
	case "write":
		return AdminScopeWrite, true
	default:
		return 0, false
	}
}

func adminScopeRank(s AdminScope) int {
	return int(s)
}

// HasAdminScope reports whether groups grant at least the required admin API scope.
// Platform admins (admin or role:admin) satisfy any scope for backwards compatibility.
// admin:write implies admin:read; admin:read does not imply write.
func HasAdminScope(groups []string, required AdminScope) bool {
	if HasRole(groups, RoleAdmin) {
		return true
	}

	max := AdminScope(0)

	for _, g := range groups {
		g = strings.ToLower(strings.TrimSpace(g))
		switch g {
		case GroupAdminRead:
			if adminScopeRank(AdminScopeRead) > adminScopeRank(max) {
				max = AdminScopeRead
			}
		case GroupAdminWrite:
			if adminScopeRank(AdminScopeWrite) > adminScopeRank(max) {
				max = AdminScopeWrite
			}
		}
	}

	return adminScopeRank(max) >= adminScopeRank(required)
}

// HasRole reports whether groups grant the given platform role.
func HasRole(groups []string, role Role) bool {
	want := string(role)
	for _, g := range groups {
		g = strings.ToLower(strings.TrimSpace(g))
		if g == want || g == "role:"+want {
			return true
		}
	}
	return false
}

// HasAnyRole reports whether groups grant at least one of the roles.
func HasAnyRole(groups []string, roles ...Role) bool {
	for _, role := range roles {
		if HasRole(groups, role) {
			return true
		}
	}
	return false
}

// HasPermission reports whether groups grant at least the required level on resource.
// Platform admins bypass resource checks. Higher levels satisfy lower ones.
func HasPermission(groups []string, resource Resource, required Level) bool {
	if HasRole(groups, RoleAdmin) {
		return true
	}

	want := string(resource)
	max := Level(0)

	for _, g := range groups {
		g = strings.ToLower(strings.TrimSpace(g))
		prefix := want + ":"
		if !strings.HasPrefix(g, prefix) {
			continue
		}
		level, ok := ParseLevel(strings.TrimPrefix(g, prefix))
		if !ok {
			continue
		}
		if levelRank(level) > levelRank(max) {
			max = level
		}
	}

	return levelRank(max) >= levelRank(required)
}
