package authorization

import (
	"context"
	"fmt"
	"strings"

	"go.uber.org/zap"

	"github.com/RyanCopley/skybridge/kms/internal/domain"
	"github.com/RyanCopley/skybridge/kms/internal/errors"
)

// ResourceType represents different types of resources
type ResourceType string

const (
	ResourceTypeApplication ResourceType = "application"
	ResourceTypeToken       ResourceType = "token"
	ResourceTypePermission  ResourceType = "permission"
	ResourceTypeUser        ResourceType = "user"
)

// Action represents different actions that can be performed
type Action string

const (
	ActionRead   Action = "read"
	ActionWrite  Action = "write"
	ActionDelete Action = "delete"
	ActionCreate Action = "create"
)

// AuthorizationContext holds context for authorization decisions
type AuthorizationContext struct {
	UserID       string
	UserEmail    string
	ResourceType ResourceType
	ResourceID   string
	Action       Action
	OwnerInfo    *domain.Owner
}

// AuthorizationService provides role-based access control
type AuthorizationService struct {
	logger *zap.Logger
}

// NewAuthorizationService creates a new authorization service
func NewAuthorizationService(logger *zap.Logger) *AuthorizationService {
	return &AuthorizationService{
		logger: logger,
	}
}

// AuthorizeResourceAccess checks if a user can perform an action on a resource
func (a *AuthorizationService) AuthorizeResourceAccess(ctx context.Context, authCtx *AuthorizationContext) error {
	if authCtx == nil {
		return errors.NewForbiddenError("Authorization context is required")
	}

	a.logger.Debug("Authorizing resource access",
		zap.String("user_id", authCtx.UserID),
		zap.String("resource_type", string(authCtx.ResourceType)),
		zap.String("resource_id", authCtx.ResourceID),
		zap.String("action", string(authCtx.Action)))

	// Check if user is a system admin
	if a.isSystemAdmin(authCtx.UserID) {
		a.logger.Debug("System admin access granted", zap.String("user_id", authCtx.UserID))
		return nil
	}

	// Check resource ownership
	if authCtx.OwnerInfo != nil {
		if a.isResourceOwner(authCtx, authCtx.OwnerInfo) {
			a.logger.Debug("Resource owner access granted",
				zap.String("user_id", authCtx.UserID),
				zap.String("resource_id", authCtx.ResourceID))
			return nil
		}
	}

	// Check specific resource-action combinations
	switch authCtx.ResourceType {
	case ResourceTypeApplication:
		return a.authorizeApplicationAccess(authCtx)
	case ResourceTypeToken:
		return a.authorizeTokenAccess(authCtx)
	case ResourceTypePermission:
		return a.authorizePermissionAccess(authCtx)
	case ResourceTypeUser:
		return a.authorizeUserAccess(authCtx)
	default:
		return errors.NewForbiddenError(fmt.Sprintf("Unknown resource type: %s", authCtx.ResourceType))
	}
}

// AuthorizeApplicationOwnership checks if a user owns an application
func (a *AuthorizationService) AuthorizeApplicationOwnership(userID string, app *domain.Application) error {
	if app == nil {
		return errors.NewValidationError("Application is required")
	}

	// System admins can access any application
	if a.isSystemAdmin(userID) {
		return nil
	}

	// Check if user is the owner
	if a.isOwner(userID, &app.Owner) {
		return nil
	}

	a.logger.Warn("Application ownership authorization failed",
		zap.String("user_id", userID),
		zap.String("app_id", app.AppID),
		zap.String("owner_type", string(app.Owner.Type)),
		zap.String("owner_name", app.Owner.Name))

	return errors.NewForbiddenError("You do not have permission to access this application")
}

// AuthorizeTokenOwnership checks if a user owns a token
func (a *AuthorizationService) AuthorizeTokenOwnership(userID string, token interface{}) error {
	// System admins can access any token
	if a.isSystemAdmin(userID) {
		return nil
	}

	// Extract owner information based on token type
	var owner *domain.Owner
	var tokenID string

	switch t := token.(type) {
	case *domain.StaticToken:
		owner = &t.Owner
		tokenID = t.ID.String()
	case *domain.UserToken:
		// For user tokens, the user ID should match
		if t.UserID == userID {
			return nil
		}
		tokenID = "user_token"
	default:
		return errors.NewValidationError("Unknown token type")
	}

	// Check ownership
	if owner != nil && a.isOwner(userID, owner) {
		return nil
	}

	a.logger.Warn("Token ownership authorization failed",
		zap.String("user_id", userID),
		zap.String("token_id", tokenID))

	return errors.NewForbiddenError("You do not have permission to access this token")
}

// isSystemAdmin checks if a user is a system administrator
func (a *AuthorizationService) isSystemAdmin(userID string) bool {
	// System admin users - this should be configurable
	systemAdmins := []string{
		"admin@example.com",
		"system@internal.com",
	}

	for _, admin := range systemAdmins {
		if userID == admin {
			return true
		}
	}

	return false
}

// isResourceOwner checks if the user is the owner of a resource
func (a *AuthorizationService) isResourceOwner(authCtx *AuthorizationContext, owner *domain.Owner) bool {
	return a.isOwner(authCtx.UserID, owner)
}

// isOwner checks if a user is the owner based on owner information
func (a *AuthorizationService) isOwner(userID string, owner *domain.Owner) bool {
	switch owner.Type {
	case domain.OwnerTypeIndividual:
		// For individual ownership, check if the user ID matches the owner name
		return userID == owner.Name || userID == owner.Owner
	case domain.OwnerTypeTeam:
		// For team ownership, this would typically require a team membership check
		// For now, we'll check if the user is the team owner
		return userID == owner.Owner || a.isTeamMember(userID, owner.Name)
	default:
		return false
	}
}

// isTeamMember checks if a user is a member of a team (placeholder implementation)
func (a *AuthorizationService) isTeamMember(userID, teamName string) bool {
	// In a real implementation, this would check team membership in a database
	// For now, we'll use a simple heuristic based on email domains
	
	if !strings.Contains(userID, "@") {
		return false
	}
	
	userDomain := strings.Split(userID, "@")[1]
	teamDomain := strings.ToLower(teamName)
	
	// Simple check: if team name looks like a domain and user's domain matches
	if strings.Contains(teamDomain, ".") && strings.Contains(userDomain, teamDomain) {
		return true
	}
	
	// Additional team membership logic would go here
	return false
}

// authorizeApplicationAccess handles application-specific authorization
func (a *AuthorizationService) authorizeApplicationAccess(authCtx *AuthorizationContext) error {
	switch authCtx.Action {
	case ActionRead:
		// Users can read applications they have some relationship with
		// This could be expanded to check for shared access, etc.
		return errors.NewForbiddenError("You do not have permission to read this application")
	case ActionWrite:
		// Only owners can modify applications
		return errors.NewForbiddenError("You do not have permission to modify this application")
	case ActionDelete:
		// Only owners can delete applications
		return errors.NewForbiddenError("You do not have permission to delete this application")
	case ActionCreate:
		// Most users can create applications (with rate limiting)
		return nil
	default:
		return errors.NewForbiddenError(fmt.Sprintf("Unknown action: %s", authCtx.Action))
	}
}

// authorizeTokenAccess handles token-specific authorization
func (a *AuthorizationService) authorizeTokenAccess(authCtx *AuthorizationContext) error {
	switch authCtx.Action {
	case ActionRead:
		return errors.NewForbiddenError("You do not have permission to read this token")
	case ActionWrite:
		return errors.NewForbiddenError("You do not have permission to modify this token")
	case ActionDelete:
		return errors.NewForbiddenError("You do not have permission to delete this token")
	case ActionCreate:
		return errors.NewForbiddenError("You do not have permission to create tokens for this application")
	default:
		return errors.NewForbiddenError(fmt.Sprintf("Unknown action: %s", authCtx.Action))
	}
}

// authorizePermissionAccess handles permission-specific authorization
func (a *AuthorizationService) authorizePermissionAccess(authCtx *AuthorizationContext) error {
	switch authCtx.Action {
	case ActionRead:
		// Users can read permissions they have
		return nil
	case ActionWrite:
		// Only admins can modify permissions
		return errors.NewForbiddenError("You do not have permission to modify permissions")
	case ActionDelete:
		// Only admins can delete permissions
		return errors.NewForbiddenError("You do not have permission to delete permissions")
	case ActionCreate:
		// Only admins can create permissions
		return errors.NewForbiddenError("You do not have permission to create permissions")
	default:
		return errors.NewForbiddenError(fmt.Sprintf("Unknown action: %s", authCtx.Action))
	}
}

// authorizeUserAccess handles user-specific authorization
func (a *AuthorizationService) authorizeUserAccess(authCtx *AuthorizationContext) error {
	switch authCtx.Action {
	case ActionRead:
		// Users can read their own information
		if authCtx.ResourceID == authCtx.UserID {
			return nil
		}
		return errors.NewForbiddenError("You do not have permission to read this user's information")
	case ActionWrite:
		// Users can modify their own information
		if authCtx.ResourceID == authCtx.UserID {
			return nil
		}
		return errors.NewForbiddenError("You do not have permission to modify this user's information")
	case ActionDelete:
		// Users can delete their own account, admins can delete any
		if authCtx.ResourceID == authCtx.UserID {
			return nil
		}
		return errors.NewForbiddenError("You do not have permission to delete this user")
	default:
		return errors.NewForbiddenError(fmt.Sprintf("Unknown action: %s", authCtx.Action))
	}
}

// AuthorizeListAccess checks if a user can list resources of a specific type
func (a *AuthorizationService) AuthorizeListAccess(ctx context.Context, userID string, resourceType ResourceType) error {
	a.logger.Debug("Authorizing list access",
		zap.String("user_id", userID),
		zap.String("resource_type", string(resourceType)))

	// System admins can list anything
	if a.isSystemAdmin(userID) {
		return nil
	}

	// For now, allow users to list their own resources
	// This would be refined based on business requirements
	switch resourceType {
	case ResourceTypeApplication:
		return nil // Users can list applications (filtered by ownership)
	case ResourceTypeToken:
		return nil // Users can list their own tokens
	case ResourceTypePermission:
		return nil // Users can list available permissions
	case ResourceTypeUser:
		// Only admins can list users
		return errors.NewForbiddenError("You do not have permission to list users")
	default:
		return errors.NewForbiddenError(fmt.Sprintf("Unknown resource type: %s", resourceType))
	}
}

// GetUserResourceFilter returns a filter for resources that a user can access
func (a *AuthorizationService) GetUserResourceFilter(userID string, resourceType ResourceType) map[string]interface{} {
	filter := make(map[string]interface{})

	// System admins see everything
	if a.isSystemAdmin(userID) {
		return filter // Empty filter means no restrictions
	}

	// Filter by ownership
	switch resourceType {
	case ResourceTypeApplication, ResourceTypeToken:
		// Users can only see resources they own
		filter["owner_email"] = userID
	case ResourceTypePermission:
		// Users can see all permissions (they're not user-specific)
		return filter
	case ResourceTypeUser:
		// Users can only see themselves
		filter["user_id"] = userID
	}

	return filter
}