package services

import (
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"
	"go.uber.org/zap"

	"github.com/kms/api-key-service/internal/crypto"
	"github.com/kms/api-key-service/internal/domain"
	"github.com/kms/api-key-service/internal/repository"
)

// tokenService implements the TokenService interface
type tokenService struct {
	tokenRepo     repository.StaticTokenRepository
	appRepo       repository.ApplicationRepository
	permRepo      repository.PermissionRepository
	grantRepo     repository.GrantedPermissionRepository
	tokenGen      *crypto.TokenGenerator
	logger        *zap.Logger
}

// NewTokenService creates a new token service
func NewTokenService(
	tokenRepo repository.StaticTokenRepository,
	appRepo repository.ApplicationRepository,
	permRepo repository.PermissionRepository,
	grantRepo repository.GrantedPermissionRepository,
	hmacKey string,
	logger *zap.Logger,
) TokenService {
	return &tokenService{
		tokenRepo: tokenRepo,
		appRepo:   appRepo,
		permRepo:  permRepo,
		grantRepo: grantRepo,
		tokenGen:  crypto.NewTokenGenerator(hmacKey),
		logger:    logger,
	}
}

// CreateStaticToken creates a new static token
func (s *tokenService) CreateStaticToken(ctx context.Context, req *domain.CreateStaticTokenRequest, userID string) (*domain.CreateStaticTokenResponse, error) {
	s.logger.Info("Creating static token", zap.String("app_id", req.AppID), zap.String("user_id", userID))

	// Validate application exists
	app, err := s.appRepo.GetByID(ctx, req.AppID)
	if err != nil {
		s.logger.Error("Failed to get application", zap.Error(err), zap.String("app_id", req.AppID))
		return nil, fmt.Errorf("application not found: %w", err)
	}

	// Validate permissions exist
	validPermissions, err := s.permRepo.ValidatePermissionScopes(ctx, req.Permissions)
	if err != nil {
		s.logger.Error("Failed to validate permissions", zap.Error(err))
		return nil, fmt.Errorf("failed to validate permissions: %w", err)
	}

	if len(validPermissions) != len(req.Permissions) {
		s.logger.Warn("Some permissions are invalid", 
			zap.Strings("requested", req.Permissions),
			zap.Strings("valid", validPermissions))
		return nil, fmt.Errorf("some requested permissions are invalid")
	}

	// Generate secure token
	tokenInfo, err := s.tokenGen.GenerateTokenWithInfo()
	if err != nil {
		s.logger.Error("Failed to generate secure token", zap.Error(err))
		return nil, fmt.Errorf("failed to generate token: %w", err)
	}

	tokenID := uuid.New()
	now := time.Now()

	// Create the token entity
	token := &domain.StaticToken{
		ID:    tokenID,
		AppID: req.AppID,
		Owner: req.Owner,
		KeyHash: tokenInfo.Hash,
		Type:    "hmac",
		CreatedAt: now,
		UpdatedAt: now,
	}

	// Save the token to the database
	err = s.tokenRepo.Create(ctx, token)
	if err != nil {
		s.logger.Error("Failed to create token in database", zap.Error(err), zap.String("token_id", tokenID.String()))
		return nil, fmt.Errorf("failed to create token: %w", err)
	}

	// Grant permissions to the token
	var grants []*domain.GrantedPermission
	for _, permScope := range validPermissions {
		// Get permission by scope to get the ID
		perm, err := s.permRepo.GetAvailablePermissionByScope(ctx, permScope)
		if err != nil {
			s.logger.Error("Failed to get permission by scope", zap.Error(err), zap.String("scope", permScope))
			continue
		}

		grant := &domain.GrantedPermission{
			ID:           uuid.New(),
			TokenType:    domain.TokenTypeStatic,
			TokenID:      tokenID,
			PermissionID: perm.ID,
			Scope:        permScope,
			CreatedBy:    userID,
		}
		grants = append(grants, grant)
	}

	if len(grants) > 0 {
		err = s.grantRepo.GrantPermissions(ctx, grants)
		if err != nil {
			s.logger.Error("Failed to grant permissions", zap.Error(err))
			// Clean up the token if permission granting fails
			s.tokenRepo.Delete(ctx, tokenID)
			return nil, fmt.Errorf("failed to grant permissions: %w", err)
		}
	}

	response := &domain.CreateStaticTokenResponse{
		ID:          tokenID,
		Token:       tokenInfo.Token, // Return the actual token only once
		Permissions: validPermissions,
		CreatedAt:   now,
	}

	s.logger.Info("Static token created successfully", 
		zap.String("token_id", tokenID.String()),
		zap.String("app_id", app.AppID),
		zap.Strings("permissions", validPermissions))
	return response, nil
}

// ListByApp lists all tokens for an application
func (s *tokenService) ListByApp(ctx context.Context, appID string, limit, offset int) ([]*domain.StaticToken, error) {
	s.logger.Debug("Listing tokens for application", zap.String("app_id", appID))

	tokens, err := s.tokenRepo.GetByAppID(ctx, appID)
	if err != nil {
		s.logger.Error("Failed to list tokens from repository", zap.Error(err), zap.String("app_id", appID))
		return nil, fmt.Errorf("failed to list tokens: %w", err)
	}

	// Apply pagination manually since GetByAppID doesn't support it
	start := offset
	end := offset + limit
	if start > len(tokens) {
		tokens = []*domain.StaticToken{}
	} else if end > len(tokens) {
		tokens = tokens[start:]
	} else {
		tokens = tokens[start:end]
	}

	s.logger.Debug("Listed tokens successfully", zap.String("app_id", appID), zap.Int("count", len(tokens)))
	return tokens, nil
}

// Delete deletes a token
func (s *tokenService) Delete(ctx context.Context, tokenID uuid.UUID, userID string) error {
	s.logger.Info("Deleting token", zap.String("token_id", tokenID.String()), zap.String("user_id", userID))

	// Check if token exists
	exists, err := s.tokenRepo.Exists(ctx, tokenID)
	if err != nil {
		s.logger.Error("Failed to check token existence", zap.Error(err), zap.String("token_id", tokenID.String()))
		return err
	}

	if !exists {
		s.logger.Error("Token not found", zap.String("token_id", tokenID.String()))
		return fmt.Errorf("token with ID '%s' not found", tokenID.String())
	}

	// Delete the token
	err = s.tokenRepo.Delete(ctx, tokenID)
	if err != nil {
		s.logger.Error("Failed to delete token", zap.Error(err), zap.String("token_id", tokenID.String()))
		return err
	}

	// TODO: Revoke associated permissions

	return nil
}

// GenerateUserToken generates a user token
func (s *tokenService) GenerateUserToken(ctx context.Context, appID, userID string, permissions []string) (string, error) {
	s.logger.Info("Generating user token", zap.String("app_id", appID), zap.String("user_id", userID))

	// TODO: Validate application
	// TODO: Validate permissions
	// TODO: Generate JWT token

	return "user-token-placeholder-" + userID, nil
}

// VerifyToken verifies a token and returns verification response
func (s *tokenService) VerifyToken(ctx context.Context, req *domain.VerifyRequest) (*domain.VerifyResponse, error) {
	s.logger.Debug("Verifying token", zap.String("app_id", req.AppID), zap.String("type", string(req.Type)))

	// TODO: Implement actual token verification logic
	response := &domain.VerifyResponse{
		Valid:       true,
		UserID:      req.UserID,
		Permissions: []string{"basic"},
		TokenType:   req.Type,
	}

	return response, nil
}

// RenewUserToken renews a user token
func (s *tokenService) RenewUserToken(ctx context.Context, req *domain.RenewRequest) (*domain.RenewResponse, error) {
	s.logger.Info("Renewing user token", zap.String("app_id", req.AppID), zap.String("user_id", req.UserID))

	// TODO: Validate current token
	// TODO: Generate new token with extended expiry but same max valid date

	response := &domain.RenewResponse{
		Token:      "renewed-token-placeholder",
		ExpiresAt:  time.Now().Add(7 * 24 * time.Hour),
		MaxValidAt: time.Now().Add(30 * 24 * time.Hour),
	}

	return response, nil
}
