package auth

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	"go.uber.org/zap"

	"github.com/kms/api-key-service/internal/cache"
	"github.com/kms/api-key-service/internal/config"
	"github.com/kms/api-key-service/internal/errors"
)

// PermissionManager handles hierarchical permission management
type PermissionManager struct {
	config       config.ConfigProvider
	logger       *zap.Logger
	cacheManager *cache.CacheManager
	hierarchy    *PermissionHierarchy
}

// NewPermissionManager creates a new permission manager
func NewPermissionManager(config config.ConfigProvider, logger *zap.Logger) *PermissionManager {
	cacheManager := cache.NewCacheManager(config, logger)
	hierarchy := NewPermissionHierarchy()
	
	return &PermissionManager{
		config:       config,
		logger:       logger,
		cacheManager: cacheManager,
		hierarchy:    hierarchy,
	}
}

// PermissionHierarchy represents the hierarchical permission structure
type PermissionHierarchy struct {
	permissions map[string]*Permission
	roles       map[string]*Role
}

// Permission represents a single permission with its hierarchy
type Permission struct {
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Parent      string   `json:"parent,omitempty"`
	Children    []string `json:"children"`
	Level       int      `json:"level"`
	Resource    string   `json:"resource"`
	Action      string   `json:"action"`
}

// Role represents a role with associated permissions
type Role struct {
	Name         string            `json:"name"`
	Description  string            `json:"description"`
	Permissions  []string          `json:"permissions"`
	Inherits     []string          `json:"inherits"`
	Metadata     map[string]string `json:"metadata"`
}

// PermissionEvaluation represents the result of permission evaluation
type PermissionEvaluation struct {
	Granted       bool              `json:"granted"`
	Permission    string            `json:"permission"`
	GrantedBy     []string          `json:"granted_by"`
	DeniedReason  string            `json:"denied_reason,omitempty"`
	Metadata      map[string]string `json:"metadata"`
	EvaluatedAt   time.Time         `json:"evaluated_at"`
}

// BulkPermissionRequest represents a bulk permission operation request
type BulkPermissionRequest struct {
	UserID      string   `json:"user_id"`
	AppID       string   `json:"app_id"`
	Permissions []string `json:"permissions"`
	Context     map[string]string `json:"context,omitempty"`
}

// BulkPermissionResponse represents a bulk permission operation response
type BulkPermissionResponse struct {
	UserID      string                          `json:"user_id"`
	AppID       string                          `json:"app_id"`
	Results     map[string]*PermissionEvaluation `json:"results"`
	EvaluatedAt time.Time                       `json:"evaluated_at"`
}

// NewPermissionHierarchy creates a new permission hierarchy
func NewPermissionHierarchy() *PermissionHierarchy {
	h := &PermissionHierarchy{
		permissions: make(map[string]*Permission),
		roles:       make(map[string]*Role),
	}
	
	// Initialize with default permissions
	h.initializeDefaultPermissions()
	h.initializeDefaultRoles()
	
	return h
}

// initializeDefaultPermissions sets up the default permission hierarchy
func (h *PermissionHierarchy) initializeDefaultPermissions() {
	defaultPermissions := []*Permission{
		// Root permissions
		{Name: "admin", Description: "Full administrative access", Level: 0, Resource: "*", Action: "*"},
		{Name: "read", Description: "Read access", Level: 0, Resource: "*", Action: "read"},
		{Name: "write", Description: "Write access", Level: 0, Resource: "*", Action: "write"},
		
		// Application permissions
		{Name: "app.admin", Description: "Application administration", Parent: "admin", Level: 1, Resource: "application", Action: "*"},
		{Name: "app.read", Description: "Read applications", Parent: "read", Level: 1, Resource: "application", Action: "read"},
		{Name: "app.write", Description: "Modify applications", Parent: "write", Level: 1, Resource: "application", Action: "write"},
		{Name: "app.create", Description: "Create applications", Parent: "app.write", Level: 2, Resource: "application", Action: "create"},
		{Name: "app.update", Description: "Update applications", Parent: "app.write", Level: 2, Resource: "application", Action: "update"},
		{Name: "app.delete", Description: "Delete applications", Parent: "app.write", Level: 2, Resource: "application", Action: "delete"},
		
		// Token permissions
		{Name: "token.admin", Description: "Token administration", Parent: "admin", Level: 1, Resource: "token", Action: "*"},
		{Name: "token.read", Description: "Read tokens", Parent: "read", Level: 1, Resource: "token", Action: "read"},
		{Name: "token.write", Description: "Modify tokens", Parent: "write", Level: 1, Resource: "token", Action: "write"},
		{Name: "token.create", Description: "Create tokens", Parent: "token.write", Level: 2, Resource: "token", Action: "create"},
		{Name: "token.revoke", Description: "Revoke tokens", Parent: "token.write", Level: 2, Resource: "token", Action: "revoke"},
		{Name: "token.verify", Description: "Verify tokens", Parent: "token.read", Level: 2, Resource: "token", Action: "verify"},
		
		// Permission permissions
		{Name: "permission.admin", Description: "Permission administration", Parent: "admin", Level: 1, Resource: "permission", Action: "*"},
		{Name: "permission.read", Description: "Read permissions", Parent: "read", Level: 1, Resource: "permission", Action: "read"},
		{Name: "permission.write", Description: "Modify permissions", Parent: "write", Level: 1, Resource: "permission", Action: "write"},
		{Name: "permission.grant", Description: "Grant permissions", Parent: "permission.write", Level: 2, Resource: "permission", Action: "grant"},
		{Name: "permission.revoke", Description: "Revoke permissions", Parent: "permission.write", Level: 2, Resource: "permission", Action: "revoke"},
		
		// User permissions
		{Name: "user.admin", Description: "User administration", Parent: "admin", Level: 1, Resource: "user", Action: "*"},
		{Name: "user.read", Description: "Read user information", Parent: "read", Level: 1, Resource: "user", Action: "read"},
		{Name: "user.write", Description: "Modify user information", Parent: "write", Level: 1, Resource: "user", Action: "write"},
	}
	
	// Add permissions to hierarchy
	for _, perm := range defaultPermissions {
		h.permissions[perm.Name] = perm
	}
	
	// Build parent-child relationships
	h.buildHierarchy()
}

// initializeDefaultRoles sets up default roles
func (h *PermissionHierarchy) initializeDefaultRoles() {
	defaultRoles := []*Role{
		{
			Name:        "super_admin",
			Description: "Super administrator with full access",
			Permissions: []string{"admin"},
			Metadata:    map[string]string{"level": "system"},
		},
		{
			Name:        "app_admin",
			Description: "Application administrator",
			Permissions: []string{"app.admin", "token.admin", "user.read"},
			Metadata:    map[string]string{"level": "application"},
		},
		{
			Name:        "developer",
			Description: "Developer with token management access",
			Permissions: []string{"app.read", "token.create", "token.read", "token.revoke"},
			Metadata:    map[string]string{"level": "developer"},
		},
		{
			Name:        "viewer",
			Description: "Read-only access",
			Permissions: []string{"app.read", "token.read", "user.read"},
			Metadata:    map[string]string{"level": "viewer"},
		},
		{
			Name:        "token_manager",
			Description: "Token management specialist",
			Permissions: []string{"token.admin", "app.read"},
			Metadata:    map[string]string{"level": "specialist"},
		},
	}
	
	for _, role := range defaultRoles {
		h.roles[role.Name] = role
	}
}

// buildHierarchy builds the parent-child relationships
func (h *PermissionHierarchy) buildHierarchy() {
	for _, perm := range h.permissions {
		if perm.Parent != "" {
			if parent, exists := h.permissions[perm.Parent]; exists {
				parent.Children = append(parent.Children, perm.Name)
			}
		}
	}
}

// HasPermission checks if a user has a specific permission
func (pm *PermissionManager) HasPermission(ctx context.Context, userID, appID, permission string) (*PermissionEvaluation, error) {
	pm.logger.Debug("Evaluating permission", 
		zap.String("user_id", userID),
		zap.String("app_id", appID),
		zap.String("permission", permission))

	// Check cache first
	cacheKey := cache.CacheKey(cache.KeyPrefixPermission, fmt.Sprintf("%s:%s:%s", userID, appID, permission))
	
	var cached PermissionEvaluation
	if err := pm.cacheManager.GetJSON(ctx, cacheKey, &cached); err == nil {
		pm.logger.Debug("Permission evaluation found in cache", 
			zap.String("permission", permission),
			zap.Bool("granted", cached.Granted))
		return &cached, nil
	}

	// Evaluate permission
	evaluation := pm.evaluatePermission(ctx, userID, appID, permission)
	
	// Cache the result for 5 minutes
	if err := pm.cacheManager.SetJSON(ctx, cacheKey, evaluation, 5*time.Minute); err != nil {
		pm.logger.Warn("Failed to cache permission evaluation", zap.Error(err))
	}

	pm.logger.Debug("Permission evaluation completed", 
		zap.String("permission", permission),
		zap.Bool("granted", evaluation.Granted),
		zap.Strings("granted_by", evaluation.GrantedBy))

	return evaluation, nil
}

// EvaluateBulkPermissions evaluates multiple permissions at once
func (pm *PermissionManager) EvaluateBulkPermissions(ctx context.Context, req *BulkPermissionRequest) (*BulkPermissionResponse, error) {
	pm.logger.Debug("Evaluating bulk permissions", 
		zap.String("user_id", req.UserID),
		zap.String("app_id", req.AppID),
		zap.Int("permission_count", len(req.Permissions)))

	response := &BulkPermissionResponse{
		UserID:      req.UserID,
		AppID:       req.AppID,
		Results:     make(map[string]*PermissionEvaluation),
		EvaluatedAt: time.Now(),
	}

	// Evaluate each permission
	for _, permission := range req.Permissions {
		evaluation, err := pm.HasPermission(ctx, req.UserID, req.AppID, permission)
		if err != nil {
			pm.logger.Error("Failed to evaluate permission in bulk operation", 
				zap.String("permission", permission),
				zap.Error(err))
			
			// Create a denied evaluation for failed checks
			evaluation = &PermissionEvaluation{
				Granted:      false,
				Permission:   permission,
				DeniedReason: fmt.Sprintf("Evaluation error: %v", err),
				EvaluatedAt:  time.Now(),
			}
		}
		
		response.Results[permission] = evaluation
	}

	pm.logger.Debug("Bulk permission evaluation completed", 
		zap.String("user_id", req.UserID),
		zap.Int("total_permissions", len(req.Permissions)),
		zap.Int("granted_count", pm.countGrantedPermissions(response.Results)))

	return response, nil
}

// evaluatePermission performs the actual permission evaluation
func (pm *PermissionManager) evaluatePermission(ctx context.Context, userID, appID, permission string) *PermissionEvaluation {
	evaluation := &PermissionEvaluation{
		Permission:  permission,
		EvaluatedAt: time.Now(),
		Metadata:    make(map[string]string),
	}

	// TODO: In a real implementation, this would:
	// 1. Fetch user roles from database
	// 2. Resolve role permissions
	// 3. Check hierarchical permissions
	// 4. Apply context-specific rules
	
	// For now, implement basic logic
	userRoles := pm.getUserRoles(ctx, userID, appID)
	grantedBy := []string{}

	// Check direct permission grants
	if pm.hasDirectPermission(userID, appID, permission) {
		grantedBy = append(grantedBy, "direct")
	}

	// Check role-based permissions
	for _, role := range userRoles {
		if pm.roleHasPermission(role, permission) {
			grantedBy = append(grantedBy, fmt.Sprintf("role:%s", role))
		}
	}

	// Check hierarchical permissions
	if len(grantedBy) == 0 {
		if inheritedPermissions := pm.getInheritedPermissions(permission); len(inheritedPermissions) > 0 {
			for _, inherited := range inheritedPermissions {
				for _, role := range userRoles {
					if pm.roleHasPermission(role, inherited) {
						grantedBy = append(grantedBy, fmt.Sprintf("inherited:%s", inherited))
						break
					}
				}
			}
		}
	}

	evaluation.Granted = len(grantedBy) > 0
	evaluation.GrantedBy = grantedBy

	if !evaluation.Granted {
		evaluation.DeniedReason = "No matching permissions or roles found"
	}

	// Add metadata
	evaluation.Metadata["user_roles"] = strings.Join(userRoles, ",")
	evaluation.Metadata["app_id"] = appID
	evaluation.Metadata["evaluation_method"] = "hierarchical"

	return evaluation
}

// getUserRoles retrieves user roles (placeholder implementation)
func (pm *PermissionManager) getUserRoles(ctx context.Context, userID, appID string) []string {
	// TODO: Implement actual role retrieval from database
	// For now, return default roles based on user patterns
	
	if strings.Contains(userID, "admin") {
		return []string{"super_admin"}
	}
	if strings.Contains(userID, "dev") {
		return []string{"developer"}
	}
	return []string{"viewer"}
}

// hasDirectPermission checks if user has direct permission grant
func (pm *PermissionManager) hasDirectPermission(userID, appID, permission string) bool {
	// TODO: Implement database lookup for direct permission grants
	return false
}

// roleHasPermission checks if a role has a specific permission
func (pm *PermissionManager) roleHasPermission(roleName, permission string) bool {
	role, exists := pm.hierarchy.roles[roleName]
	if !exists {
		return false
	}

	// Check direct permissions
	for _, perm := range role.Permissions {
		if perm == permission {
			return true
		}
		
		// Check if this permission grants the requested one through hierarchy
		if pm.permissionIncludes(perm, permission) {
			return true
		}
	}

	// Check inherited roles
	for _, inheritedRole := range role.Inherits {
		if pm.roleHasPermission(inheritedRole, permission) {
			return true
		}
	}

	return false
}

// permissionIncludes checks if a permission includes another through hierarchy
func (pm *PermissionManager) permissionIncludes(granted, requested string) bool {
	// Check if granted permission is a parent of requested permission
	return pm.isPermissionParent(granted, requested)
}

// isPermissionParent checks if one permission is a parent of another
func (pm *PermissionManager) isPermissionParent(parent, child string) bool {
	childPerm, exists := pm.hierarchy.permissions[child]
	if !exists {
		return false
	}

	// Traverse up the hierarchy
	current := childPerm.Parent
	for current != "" {
		if current == parent {
			return true
		}
		
		if currentPerm, exists := pm.hierarchy.permissions[current]; exists {
			current = currentPerm.Parent
		} else {
			break
		}
	}

	return false
}

// getInheritedPermissions gets permissions that could grant the requested permission
func (pm *PermissionManager) getInheritedPermissions(permission string) []string {
	var inherited []string
	
	perm, exists := pm.hierarchy.permissions[permission]
	if !exists {
		return inherited
	}

	// Get all parent permissions
	current := perm.Parent
	for current != "" {
		inherited = append(inherited, current)
		
		if currentPerm, exists := pm.hierarchy.permissions[current]; exists {
			current = currentPerm.Parent
		} else {
			break
		}
	}

	return inherited
}

// countGrantedPermissions counts granted permissions in bulk results
func (pm *PermissionManager) countGrantedPermissions(results map[string]*PermissionEvaluation) int {
	count := 0
	for _, eval := range results {
		if eval.Granted {
			count++
		}
	}
	return count
}

// GetPermissionHierarchy returns the current permission hierarchy
func (pm *PermissionManager) GetPermissionHierarchy() *PermissionHierarchy {
	return pm.hierarchy
}

// AddPermission adds a new permission to the hierarchy
func (pm *PermissionManager) AddPermission(permission *Permission) error {
	if permission.Name == "" {
		return errors.NewValidationError("Permission name is required")
	}

	// Validate parent exists if specified
	if permission.Parent != "" {
		if _, exists := pm.hierarchy.permissions[permission.Parent]; !exists {
			return errors.NewValidationError(fmt.Sprintf("Parent permission '%s' does not exist", permission.Parent))
		}
	}

	pm.hierarchy.permissions[permission.Name] = permission
	pm.hierarchy.buildHierarchy()

	pm.logger.Info("Permission added to hierarchy", 
		zap.String("permission", permission.Name),
		zap.String("parent", permission.Parent))

	return nil
}

// AddRole adds a new role to the system
func (pm *PermissionManager) AddRole(role *Role) error {
	if role.Name == "" {
		return errors.NewValidationError("Role name is required")
	}

	// Validate permissions exist
	for _, perm := range role.Permissions {
		if _, exists := pm.hierarchy.permissions[perm]; !exists {
			return errors.NewValidationError(fmt.Sprintf("Permission '%s' does not exist", perm))
		}
	}

	// Validate inherited roles exist
	for _, inheritedRole := range role.Inherits {
		if _, exists := pm.hierarchy.roles[inheritedRole]; !exists {
			return errors.NewValidationError(fmt.Sprintf("Inherited role '%s' does not exist", inheritedRole))
		}
	}

	pm.hierarchy.roles[role.Name] = role

	pm.logger.Info("Role added to system", 
		zap.String("role", role.Name),
		zap.Strings("permissions", role.Permissions))

	return nil
}

// ListPermissions returns all permissions sorted by hierarchy
func (pm *PermissionManager) ListPermissions() []*Permission {
	permissions := make([]*Permission, 0, len(pm.hierarchy.permissions))
	
	for _, perm := range pm.hierarchy.permissions {
		permissions = append(permissions, perm)
	}

	// Sort by level and name
	sort.Slice(permissions, func(i, j int) bool {
		if permissions[i].Level != permissions[j].Level {
			return permissions[i].Level < permissions[j].Level
		}
		return permissions[i].Name < permissions[j].Name
	})

	return permissions
}

// ListRoles returns all roles
func (pm *PermissionManager) ListRoles() []*Role {
	roles := make([]*Role, 0, len(pm.hierarchy.roles))
	
	for _, role := range pm.hierarchy.roles {
		roles = append(roles, role)
	}

	// Sort by name
	sort.Slice(roles, func(i, j int) bool {
		return roles[i].Name < roles[j].Name
	})

	return roles
}

// InvalidatePermissionCache invalidates cached permission evaluations for a user
func (pm *PermissionManager) InvalidatePermissionCache(ctx context.Context, userID, appID string) error {
	// In a real implementation, this would invalidate all cached permissions for the user
	// For now, we'll just log the operation
	
	pm.logger.Info("Invalidating permission cache", 
		zap.String("user_id", userID),
		zap.String("app_id", appID))

	return nil
}

// ListPermissions returns all permissions sorted by hierarchy (for PermissionHierarchy)
func (h *PermissionHierarchy) ListPermissions() []*Permission {
	permissions := make([]*Permission, 0, len(h.permissions))
	
	for _, perm := range h.permissions {
		permissions = append(permissions, perm)
	}

	// Sort by level and name
	sort.Slice(permissions, func(i, j int) bool {
		if permissions[i].Level != permissions[j].Level {
			return permissions[i].Level < permissions[j].Level
		}
		return permissions[i].Name < permissions[j].Name
	})

	return permissions
}

// ListRoles returns all roles (for PermissionHierarchy)
func (h *PermissionHierarchy) ListRoles() []*Role {
	roles := make([]*Role, 0, len(h.roles))
	
	for _, role := range h.roles {
		roles = append(roles, role)
	}

	// Sort by name
	sort.Slice(roles, func(i, j int) bool {
		return roles[i].Name < roles[j].Name
	})

	return roles
}
