package client

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/yaoapp/gou/store"
	"github.com/yaoapp/yao/openapi/oauth/types"
)

// DefaultClient provides a default implementation of ClientProvider
type DefaultClient struct {
	prefix string
	cache  store.Store
	store  store.Store
}

// DefaultClientOptions provides options for the DefaultClient
type DefaultClientOptions struct {
	Prefix string
	Cache  store.Store
	Store  store.Store
}

// NewDefaultClient creates a new DefaultClient
func NewDefaultClient(options *DefaultClientOptions) (*DefaultClient, error) {
	if options == nil {
		return nil, types.ErrInvalidConfiguration
	}

	if options.Store == nil {
		return nil, types.ErrStoreMissing
	}

	if options.Prefix == "" {
		options.Prefix = "__yao:"
	}

	return &DefaultClient{
		prefix: options.Prefix,
		cache:  options.Cache,
		store:  options.Store,
	}, nil
}

// Key generation methods

func (c *DefaultClient) clientKey(clientID string) string {
	return fmt.Sprintf("%soauth:client:%s", c.prefix, clientID)
}

func (c *DefaultClient) clientListKey() string {
	return fmt.Sprintf("%soauth:clients", c.prefix)
}

// GetClientByID retrieves client information using a client ID
func (c *DefaultClient) GetClientByID(ctx context.Context, clientID string) (*types.ClientInfo, error) {
	// Try cache first if available
	if c.cache != nil {
		if cached, ok := c.cache.Get(c.clientKey(clientID)); ok {
			if clientInfo, ok := cached.(*types.ClientInfo); ok {
				return clientInfo, nil
			}
		}
	}

	// Fallback to store
	key := c.clientKey(clientID)
	data, ok := c.store.Get(key)
	if !ok {
		return nil, &types.ErrorResponse{
			Code:             types.ErrorInvalidClient,
			ErrorDescription: "Client not found",
		}
	}

	var clientInfo *types.ClientInfo

	// Handle different data types returned by different stores
	switch v := data.(type) {
	case *types.ClientInfo:
		// Direct object (from cache)
		clientInfo = v
	case map[string]interface{}:
		// Map with JSON field names (standard format)
		jsonData, err := json.Marshal(v)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal map data: %w", err)
		}
		clientInfo = &types.ClientInfo{}
		if err := json.Unmarshal(jsonData, clientInfo); err != nil {
			return nil, fmt.Errorf("failed to unmarshal client data: %w", err)
		}
	case []byte:
		// Byte data (for backward compatibility)
		clientInfo = &types.ClientInfo{}
		if err := json.Unmarshal(v, clientInfo); err != nil {
			return nil, fmt.Errorf("failed to unmarshal client data: %w", err)
		}
	case string:
		// String data (for backward compatibility)
		clientInfo = &types.ClientInfo{}
		if err := json.Unmarshal([]byte(v), clientInfo); err != nil {
			return nil, fmt.Errorf("failed to unmarshal client data: %w", err)
		}
	default:
		// Try JSON marshaling as fallback for unknown types
		jsonData, err := json.Marshal(data)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal data to JSON: %w", err)
		}
		clientInfo = &types.ClientInfo{}
		if err := json.Unmarshal(jsonData, clientInfo); err != nil {
			return nil, fmt.Errorf("failed to unmarshal client data: %w", err)
		}
	}

	// Cache the result if cache is available
	if c.cache != nil {
		c.cache.Set(c.clientKey(clientID), clientInfo, 5*time.Minute) // Cache for 5 minutes
	}

	return clientInfo, nil
}

// GetClientByCredentials retrieves and validates client using client credentials
func (c *DefaultClient) GetClientByCredentials(ctx context.Context, clientID string, clientSecret string) (*types.ClientInfo, error) {
	clientInfo, err := c.GetClientByID(ctx, clientID)
	if err != nil {
		return nil, err
	}

	// For public clients, no secret validation required
	if clientInfo.ClientType == types.ClientTypePublic {
		return clientInfo, nil
	}

	// For confidential clients, validate secret
	if clientInfo.ClientSecret != clientSecret {
		return nil, &types.ErrorResponse{
			Code:             types.ErrorInvalidClient,
			ErrorDescription: "Invalid client credentials",
		}
	}

	return clientInfo, nil
}

// CreateClient creates a new OAuth client and returns the client information
func (c *DefaultClient) CreateClient(ctx context.Context, clientInfo *types.ClientInfo) (*types.ClientInfo, error) {
	// Check for nil client info
	if clientInfo == nil {
		return nil, &types.ErrorResponse{
			Code:             types.ErrorInvalidRequest,
			ErrorDescription: "Client information is required",
		}
	}

	// Validate required fields
	if clientInfo.ClientID == "" {
		return nil, &types.ErrorResponse{
			Code:             types.ErrorInvalidRequest,
			ErrorDescription: "Client ID is required",
		}
	}

	// Check if client already exists
	existing, err := c.GetClientByID(ctx, clientInfo.ClientID)
	if err == nil && existing != nil {
		return nil, &types.ErrorResponse{
			Code:             types.ErrorInvalidClient,
			ErrorDescription: "Client already exists",
		}
	}

	// Set timestamps
	now := time.Now()
	clientInfo.CreatedAt = now
	clientInfo.UpdatedAt = now

	// Set defaults
	if clientInfo.ClientType == "" {
		clientInfo.ClientType = types.ClientTypeConfidential
	}

	// Validate client
	validationResult, err := c.ValidateClient(ctx, clientInfo)
	if err != nil {
		return nil, err
	}
	if !validationResult.Valid {
		return nil, &types.ErrorResponse{
			Code:             types.ErrorInvalidRequest,
			ErrorDescription: strings.Join(validationResult.Errors, "; "),
		}
	}

	// Save client data
	if err := c.saveClient(ctx, clientInfo); err != nil {
		return nil, err
	}

	// Add to client list
	if err := c.addToClientList(ctx, clientInfo.ClientID); err != nil {
		return nil, err
	}

	// Cache the client if cache is available
	if c.cache != nil {
		c.cache.Set(c.clientKey(clientInfo.ClientID), clientInfo, 5*time.Minute)
	}

	return clientInfo, nil
}

// UpdateClient updates an existing OAuth client configuration
func (c *DefaultClient) UpdateClient(ctx context.Context, clientID string, clientInfo *types.ClientInfo) (*types.ClientInfo, error) {
	// Check for nil client info
	if clientInfo == nil {
		return nil, &types.ErrorResponse{
			Code:             types.ErrorInvalidRequest,
			ErrorDescription: "Client information is required",
		}
	}

	// Check if client exists
	existing, err := c.GetClientByID(ctx, clientID)
	if err != nil {
		return nil, err
	}

	// Update client ID if provided
	if clientInfo.ClientID != "" && clientInfo.ClientID != clientID {
		return nil, &types.ErrorResponse{
			Code:             types.ErrorInvalidRequest,
			ErrorDescription: "Cannot change client ID",
		}
	}

	// Set client ID and preserve creation time
	clientInfo.ClientID = clientID
	clientInfo.CreatedAt = existing.CreatedAt
	clientInfo.UpdatedAt = time.Now()

	// Validate client
	validationResult, err := c.ValidateClient(ctx, clientInfo)
	if err != nil {
		return nil, err
	}
	if !validationResult.Valid {
		return nil, &types.ErrorResponse{
			Code:             types.ErrorInvalidRequest,
			ErrorDescription: strings.Join(validationResult.Errors, "; "),
		}
	}

	// Save updated client data
	if err := c.saveClient(ctx, clientInfo); err != nil {
		return nil, err
	}

	// Update cache if available
	if c.cache != nil {
		c.cache.Set(c.clientKey(clientID), clientInfo, 5*time.Minute)
	}

	return clientInfo, nil
}

// DeleteClient removes an OAuth client from the system
func (c *DefaultClient) DeleteClient(ctx context.Context, clientID string) error {
	// Check if client exists
	_, err := c.GetClientByID(ctx, clientID)
	if err != nil {
		return err
	}

	// Remove from client list
	if err := c.removeFromClientList(ctx, clientID); err != nil {
		return err
	}

	// Delete client data
	key := c.clientKey(clientID)
	if err := c.store.Del(key); err != nil {
		return fmt.Errorf("failed to delete client: %w", err)
	}

	// Clear cache if available
	if c.cache != nil {
		c.cache.Del(c.clientKey(clientID))
	}

	return nil
}

// ValidateClient validates client information and configuration
func (c *DefaultClient) ValidateClient(ctx context.Context, clientInfo *types.ClientInfo) (*types.ValidationResult, error) {
	result := &types.ValidationResult{Valid: true}

	// Validate client ID
	if clientInfo.ClientID == "" {
		result.Valid = false
		result.Errors = append(result.Errors, "Client ID is required")
	}

	// Validate client type
	if clientInfo.ClientType != types.ClientTypeConfidential &&
		clientInfo.ClientType != types.ClientTypePublic &&
		clientInfo.ClientType != types.ClientTypeCredentialed {
		result.Valid = false
		result.Errors = append(result.Errors, "Invalid client type")
	}

	// Validate client secret for confidential clients
	if clientInfo.ClientType == types.ClientTypeConfidential && clientInfo.ClientSecret == "" {
		result.Valid = false
		result.Errors = append(result.Errors, "Client secret is required for confidential clients")
	}

	// Validate redirect URIs
	if len(clientInfo.RedirectURIs) == 0 && (strings.Contains(clientInfo.Scope, "openid") || strings.Contains(clientInfo.Scope, "profile") || strings.Contains(clientInfo.Scope, "email")) {
		result.Valid = false
		result.Errors = append(result.Errors, "At least one redirect URI is required for openid, profile, or email scope")
	}

	// Validate grant types
	if len(clientInfo.GrantTypes) == 0 {
		clientInfo.GrantTypes = []string{types.GrantTypeAuthorizationCode}
	}

	// Validate response types
	if len(clientInfo.ResponseTypes) == 0 {
		clientInfo.ResponseTypes = []string{types.ResponseTypeCode}
	}

	return result, nil
}

// ListClients retrieves a list of clients with optional filtering
func (c *DefaultClient) ListClients(ctx context.Context, filters map[string]interface{}, limit int, offset int) ([]*types.ClientInfo, int, error) {
	// Get client list
	clientIDs, err := c.getClientList(ctx)
	if err != nil {
		return nil, 0, err
	}

	var clients []*types.ClientInfo

	// Load all clients
	for _, clientID := range clientIDs {
		client, err := c.GetClientByID(ctx, clientID)
		if err != nil {
			continue // Skip invalid clients
		}

		// Apply filters
		if c.matchesFilters(client, filters) {
			clients = append(clients, client)
		}
	}

	total := len(clients)

	// Apply pagination
	if offset > 0 {
		if offset >= len(clients) {
			return []*types.ClientInfo{}, total, nil
		}
		clients = clients[offset:]
	}

	if limit > 0 && len(clients) > limit {
		clients = clients[:limit]
	}

	return clients, total, nil
}

// ValidateRedirectURI validates if a redirect URI is registered for the client
func (c *DefaultClient) ValidateRedirectURI(ctx context.Context, clientID string, redirectURI string) (*types.ValidationResult, error) {
	client, err := c.GetClientByID(ctx, clientID)
	if err != nil {
		return nil, err
	}

	result := &types.ValidationResult{Valid: false}

	for _, uri := range client.RedirectURIs {
		if uri == redirectURI {
			result.Valid = true
			break
		}
	}

	if !result.Valid {
		result.Errors = append(result.Errors, "Redirect URI not registered for this client")
	}

	return result, nil
}

// ValidateScope validates if the client is authorized to request specific scopes
func (c *DefaultClient) ValidateScope(ctx context.Context, clientID string, scopes []string) (*types.ValidationResult, error) {
	client, err := c.GetClientByID(ctx, clientID)
	if err != nil {
		return nil, err
	}

	result := &types.ValidationResult{Valid: true}

	// If client has no scope restrictions, allow all scopes
	if client.Scope == "" {
		return result, nil
	}

	// Parse client allowed scopes
	allowedScopes := strings.Fields(client.Scope)
	allowedScopeMap := make(map[string]bool)
	for _, scope := range allowedScopes {
		allowedScopeMap[scope] = true
	}

	// Check each requested scope
	for _, scope := range scopes {
		if !allowedScopeMap[scope] {
			result.Valid = false
			result.Errors = append(result.Errors, fmt.Sprintf("Scope '%s' not allowed for this client", scope))
		}
	}

	return result, nil
}

// IsClientActive checks if a client is active and can be used for authentication
func (c *DefaultClient) IsClientActive(ctx context.Context, clientID string) (bool, error) {
	client, err := c.GetClientByID(ctx, clientID)
	if err != nil {
		return false, err
	}

	// For now, all existing clients are considered active
	// This can be extended to check additional status fields
	return client != nil, nil
}

// Helper methods

func (c *DefaultClient) saveClient(ctx context.Context, clientInfo *types.ClientInfo) error {
	key := c.clientKey(clientInfo.ClientID)

	// Convert to map[string]interface{} using JSON serialization to ensure consistent field names
	jsonData, err := json.Marshal(clientInfo)
	if err != nil {
		return fmt.Errorf("failed to marshal client data: %w", err)
	}

	var clientMap map[string]interface{}
	if err := json.Unmarshal(jsonData, &clientMap); err != nil {
		return fmt.Errorf("failed to unmarshal to map: %w", err)
	}

	// Store the map - this ensures JSON field names are used consistently
	if err := c.store.Set(key, clientMap, 0); err != nil {
		return fmt.Errorf("failed to save client: %w", err)
	}

	return nil
}

func (c *DefaultClient) getClientList(ctx context.Context) ([]string, error) {
	// Try cache first if available
	if c.cache != nil {
		if cached, ok := c.cache.Get(c.clientListKey()); ok {
			if clientIDs, ok := cached.([]string); ok {
				return clientIDs, nil
			}
		}
	}

	// Fallback to store
	key := c.clientListKey()
	data, ok := c.store.Get(key)
	if !ok {
		return []string{}, nil
	}

	var clientIDs []string

	// Handle different data types returned by different stores
	switch v := data.(type) {
	case []string:
		// Direct slice (from stores that preserve slice types)
		clientIDs = v
	case []interface{}:
		// Interface slice (from stores that decode to interface slices)
		for _, item := range v {
			if str, ok := item.(string); ok {
				clientIDs = append(clientIDs, str)
			}
		}
	case []byte:
		// Byte data (for backward compatibility)
		if err := json.Unmarshal(v, &clientIDs); err != nil {
			return nil, fmt.Errorf("failed to unmarshal client list: %w", err)
		}
	case string:
		// String data (for backward compatibility)
		if err := json.Unmarshal([]byte(v), &clientIDs); err != nil {
			return nil, fmt.Errorf("failed to unmarshal client list: %w", err)
		}
	default:
		// Handle MongoDB primitive types and other BSON types
		jsonData, err := json.Marshal(data)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal data to JSON: %w", err)
		}
		if err := json.Unmarshal(jsonData, &clientIDs); err != nil {
			return nil, fmt.Errorf("failed to unmarshal client list: %w", err)
		}
	}

	// Cache the result if cache is available
	if c.cache != nil {
		c.cache.Set(c.clientListKey(), clientIDs, 5*time.Minute)
	}

	return clientIDs, nil
}

func (c *DefaultClient) saveClientList(ctx context.Context, clientIDs []string) error {
	key := c.clientListKey()

	// Store the slice directly - this should work consistently across stores
	if err := c.store.Set(key, clientIDs, 0); err != nil {
		return fmt.Errorf("failed to save client list: %w", err)
	}

	// Update cache if available
	if c.cache != nil {
		c.cache.Set(c.clientListKey(), clientIDs, 5*time.Minute)
	}

	return nil
}

func (c *DefaultClient) addToClientList(ctx context.Context, clientID string) error {
	clientIDs, err := c.getClientList(ctx)
	if err != nil {
		return err
	}

	// Check if already exists
	for _, id := range clientIDs {
		if id == clientID {
			return nil // Already exists
		}
	}

	clientIDs = append(clientIDs, clientID)

	// Clear cache first to ensure consistency
	if c.cache != nil {
		c.cache.Del(c.clientListKey())
	}

	return c.saveClientList(ctx, clientIDs)
}

func (c *DefaultClient) removeFromClientList(ctx context.Context, clientID string) error {
	clientIDs, err := c.getClientList(ctx)
	if err != nil {
		return err
	}

	// Remove client ID
	var newClientIDs []string
	for _, id := range clientIDs {
		if id != clientID {
			newClientIDs = append(newClientIDs, id)
		}
	}

	// Clear cache first to ensure consistency
	if c.cache != nil {
		c.cache.Del(c.clientListKey())
	}

	return c.saveClientList(ctx, newClientIDs)
}

func (c *DefaultClient) matchesFilters(client *types.ClientInfo, filters map[string]interface{}) bool {
	if filters == nil {
		return true
	}

	for key, value := range filters {
		switch key {
		case "client_type":
			if client.ClientType != value.(string) {
				return false
			}
		case "client_name":
			if client.ClientName != value.(string) {
				return false
			}
		case "application_type":
			if client.ApplicationType != value.(string) {
				return false
			}
		}
	}

	return true
}
