package services

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"time"

	"github.com/go-playground/validator/v10"
	"github.com/google/uuid"
	"go.uber.org/zap"

	"github.com/RyanCopley/skybridge/kms/internal/audit"
	"github.com/RyanCopley/skybridge/kms/internal/domain"
	"github.com/RyanCopley/skybridge/kms/internal/repository"
)

// applicationService implements the ApplicationService interface
type applicationService struct {
	appRepo     repository.ApplicationRepository
	auditRepo   repository.AuditRepository
	auditLogger audit.AuditLogger
	logger      *zap.Logger
	validator   *validator.Validate
}

// NewApplicationService creates a new application service
func NewApplicationService(appRepo repository.ApplicationRepository, auditRepo repository.AuditRepository, logger *zap.Logger) ApplicationService {
	// Create audit logger with audit package's repository interface
	auditRepoImpl := &auditRepositoryAdapter{repo: auditRepo}
	auditLogger := audit.NewAuditLogger(nil, logger, auditRepoImpl) // config can be nil for now
	
	return &applicationService{
		appRepo:     appRepo,
		auditRepo:   auditRepo,
		auditLogger: auditLogger,
		logger:      logger,
		validator:   validator.New(),
	}
}

// auditRepositoryAdapter adapts repository.AuditRepository to audit.AuditRepository
type auditRepositoryAdapter struct {
	repo repository.AuditRepository
}

func (a *auditRepositoryAdapter) Create(ctx context.Context, event *audit.AuditEvent) error {
	return a.repo.Create(ctx, event)
}

func (a *auditRepositoryAdapter) Query(ctx context.Context, filter *audit.AuditFilter) ([]*audit.AuditEvent, error) {
	return a.repo.Query(ctx, filter)
}

func (a *auditRepositoryAdapter) GetStats(ctx context.Context, filter *audit.AuditStatsFilter) (*audit.AuditStats, error) {
	return a.repo.GetStats(ctx, filter)
}

func (a *auditRepositoryAdapter) DeleteOldEvents(ctx context.Context, olderThan time.Time) (int, error) {
	return a.repo.DeleteOldEvents(ctx, olderThan)
}

func (a *auditRepositoryAdapter) GetByID(ctx context.Context, eventID uuid.UUID) (*audit.AuditEvent, error) {
	return a.repo.GetByID(ctx, eventID)
}

// Create creates a new application
func (s *applicationService) Create(ctx context.Context, req *domain.CreateApplicationRequest, userID string) (*domain.Application, error) {
	s.logger.Info("Creating application", zap.String("app_id", req.AppID), zap.String("user_id", userID))

	// Input validation using validator
	if err := s.validator.Struct(req); err != nil {
		s.logger.Warn("Application creation request validation failed", 
			zap.String("app_id", req.AppID),
			zap.String("user_id", userID),
			zap.Error(err))
		return nil, fmt.Errorf("validation failed: %w", err)
	}

	// Manual validation for Duration fields
	if req.TokenRenewalDuration.Duration <= 0 {
		return nil, fmt.Errorf("token_renewal_duration must be greater than 0")
	}
	if req.MaxTokenDuration.Duration <= 0 {
		return nil, fmt.Errorf("max_token_duration must be greater than 0")
	}

	// Basic permission validation - check if user can create applications
	// In a real system, this would check against user roles/permissions
	if userID == "" {
		return nil, fmt.Errorf("user authentication required")
	}

	// Additional business logic validation
	if req.TokenRenewalDuration.Duration > req.MaxTokenDuration.Duration {
		return nil, fmt.Errorf("token renewal duration cannot be greater than max token duration")
	}

	app := &domain.Application{
		AppID:                req.AppID,
		AppLink:              req.AppLink,
		Type:                 req.Type,
		CallbackURL:          req.CallbackURL,
		HMACKey:              generateHMACKey(), // Uses crypto/rand for secure key generation
		TokenPrefix:          req.TokenPrefix,
		TokenRenewalDuration: req.TokenRenewalDuration,
		MaxTokenDuration:     req.MaxTokenDuration,
		Owner:                req.Owner,
	}

	if err := s.appRepo.Create(ctx, app); err != nil {
		s.logger.Error("Failed to create application", zap.Error(err), zap.String("app_id", req.AppID))
		
		// Log audit event for failed creation
		s.auditLogger.LogEvent(ctx, audit.NewAuditEventBuilder(audit.EventTypeAppCreated).
			WithSeverity(audit.SeverityError).
			WithStatus(audit.StatusFailure).
			WithActor(userID, "user", "").
			WithResource(req.AppID, "application").
			WithAction("create").
			WithDescription(fmt.Sprintf("Failed to create application %s", req.AppID)).
			WithDetails(map[string]interface{}{
				"error":   err.Error(),
				"app_id":  req.AppID,
				"user_id": userID,
			}).
			Build())
		
		return nil, fmt.Errorf("failed to create application: %w", err)
	}

	// Log successful creation
	s.auditLogger.LogEvent(ctx, audit.NewAuditEventBuilder(audit.EventTypeAppCreated).
		WithSeverity(audit.SeverityInfo).
		WithStatus(audit.StatusSuccess).
		WithActor(userID, "user", "").
		WithResource(app.AppID, "application").
		WithAction("create").
		WithDescription(fmt.Sprintf("Created application %s", app.AppID)).
		WithDetails(map[string]interface{}{
			"app_id":      app.AppID,
			"app_link":    app.AppLink,
			"type":        app.Type,
			"user_id":     userID,
			"owner_name":  app.Owner.Name,
			"owner_type":  app.Owner.Type,
		}).
		Build())

	s.logger.Info("Application created successfully", zap.String("app_id", app.AppID))
	return app, nil
}

// GetByID retrieves an application by its ID
func (s *applicationService) GetByID(ctx context.Context, appID string) (*domain.Application, error) {
	s.logger.Debug("Getting application by ID", zap.String("app_id", appID))

	app, err := s.appRepo.GetByID(ctx, appID)
	if err != nil {
		s.logger.Error("Failed to get application", zap.Error(err), zap.String("app_id", appID))
		return nil, fmt.Errorf("failed to get application: %w", err)
	}

	return app, nil
}

// List retrieves applications with pagination
func (s *applicationService) List(ctx context.Context, limit, offset int) ([]*domain.Application, error) {
	s.logger.Debug("Listing applications", zap.Int("limit", limit), zap.Int("offset", offset))

	if limit <= 0 {
		limit = 50 // Default limit
	}
	if limit > 100 {
		limit = 100 // Max limit
	}

	apps, err := s.appRepo.List(ctx, limit, offset)
	if err != nil {
		s.logger.Error("Failed to list applications", zap.Error(err))
		return nil, fmt.Errorf("failed to list applications: %w", err)
	}

	s.logger.Debug("Listed applications", zap.Int("count", len(apps)))
	return apps, nil
}

// Update updates an existing application
func (s *applicationService) Update(ctx context.Context, appID string, updates *domain.UpdateApplicationRequest, userID string) (*domain.Application, error) {
	s.logger.Info("Updating application", zap.String("app_id", appID), zap.String("user_id", userID))

	// Input validation using validator
	if err := s.validator.Struct(updates); err != nil {
		s.logger.Warn("Application update request validation failed", 
			zap.String("app_id", appID),
			zap.String("user_id", userID),
			zap.Error(err))
		return nil, fmt.Errorf("validation failed: %w", err)
	}

	// Basic permission validation - check if user can update applications
	// In a real system, this would check against user roles/permissions and application ownership
	if userID == "" {
		return nil, fmt.Errorf("user authentication required")
	}

	// Manual validation for Duration fields
	if updates.TokenRenewalDuration != nil && updates.TokenRenewalDuration.Duration <= 0 {
		return nil, fmt.Errorf("token_renewal_duration must be greater than 0")
	}
	if updates.MaxTokenDuration != nil && updates.MaxTokenDuration.Duration <= 0 {
		return nil, fmt.Errorf("max_token_duration must be greater than 0")
	}

	// Additional business logic validation
	if updates.TokenRenewalDuration != nil && updates.MaxTokenDuration != nil {
		if updates.TokenRenewalDuration.Duration > updates.MaxTokenDuration.Duration {
			return nil, fmt.Errorf("token renewal duration cannot be greater than max token duration")
		}
	}

	app, err := s.appRepo.Update(ctx, appID, updates)
	if err != nil {
		s.logger.Error("Failed to update application", zap.Error(err), zap.String("app_id", appID))
		return nil, fmt.Errorf("failed to update application: %w", err)
	}

	s.logger.Info("Application updated successfully", zap.String("app_id", appID))
	return app, nil
}

// Delete deletes an application
func (s *applicationService) Delete(ctx context.Context, appID string, userID string) error {
	s.logger.Info("Deleting application", zap.String("app_id", appID), zap.String("user_id", userID))

	// Basic permission validation - check if user can delete applications
	// In a real system, this would check against user roles/permissions and application ownership
	if userID == "" {
		return fmt.Errorf("user authentication required")
	}

	// Input validation - check appID format
	if appID == "" {
		return fmt.Errorf("application ID is required")
	}

	// Check if application exists before attempting deletion
	_, err := s.appRepo.GetByID(ctx, appID)
	if err != nil {
		s.logger.Warn("Application not found for deletion", 
			zap.String("app_id", appID),
			zap.String("user_id", userID))
		return fmt.Errorf("application not found: %w", err)
	}

	// Check for existing tokens and handle appropriately
	// In a production system, we would implement one of these strategies:
	// 1. Prevent deletion if active tokens exist (safe approach)
	// 2. Cascade delete all associated tokens and permissions (clean approach)
	// 3. Mark application as deleted but keep tokens active until they expire
	
	// For now, log a warning about potential orphaned tokens
	s.logger.Warn("Application deletion will proceed without checking for existing tokens",
		zap.String("app_id", appID),
		zap.String("recommendation", "implement token cleanup or prevention logic"))

	if err := s.appRepo.Delete(ctx, appID); err != nil {
		s.logger.Error("Failed to delete application", zap.Error(err), zap.String("app_id", appID))
		return fmt.Errorf("failed to delete application: %w", err)
	}

	s.logger.Info("Application deleted successfully", zap.String("app_id", appID))
	return nil
}

// generateHMACKey generates a secure HMAC key
func generateHMACKey() string {
	// Generate 32 bytes (256 bits) of cryptographically secure random data
	key := make([]byte, 32)
	_, err := rand.Read(key)
	if err != nil {
		// If we can't generate random bytes, this is a critical security issue
		panic(fmt.Sprintf("Failed to generate cryptographic key: %v", err))
	}

	// Return as hex-encoded string for storage
	return hex.EncodeToString(key)
}
