// Filename: internal/repository/auth_token.go

package repository

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"gemini-balancer/internal/crypto"
	"gemini-balancer/internal/models"

	"github.com/sirupsen/logrus"
	"gorm.io/gorm"
)

// AuthTokenRepository defines the interface for AuthToken data access.
type AuthTokenRepository interface {
	GetAllTokensWithGroups() ([]*models.AuthToken, error)
	BatchUpdateTokens(updates []*models.TokenUpdateRequest) error
	GetTokenByHashedValue(tokenHash string) (*models.AuthToken, error)
	SeedAdminToken(encryptedToken, tokenHash string) error
}

type gormAuthTokenRepository struct {
	db     *gorm.DB
	crypto *crypto.Service
	logger *logrus.Entry
}

func NewAuthTokenRepository(db *gorm.DB, crypto *crypto.Service, logger *logrus.Logger) AuthTokenRepository {
	return &gormAuthTokenRepository{
		db:     db,
		crypto: crypto,
		logger: logger.WithField("component", "repository.authToken🔐"),
	}
}

// GetAllTokensWithGroups fetches all tokens and decrypts them for use in services.
func (r *gormAuthTokenRepository) GetAllTokensWithGroups() ([]*models.AuthToken, error) {
	var tokens []*models.AuthToken
	if err := r.db.Preload("AllowedGroups").Find(&tokens).Error; err != nil {
		return nil, err
	}
	// [CRITICAL] Decrypt all tokens before returning them.
	if err := r.decryptTokens(tokens); err != nil {
		// Log the error but return the partially decrypted data, as some might be usable.
		r.logger.WithError(err).Error("Batch decryption failed for some auth tokens.")
	}
	return tokens, nil
}

// BatchUpdateTokens provides a transactional way to update all tokens, handling encryption.
func (r *gormAuthTokenRepository) BatchUpdateTokens(updates []*models.TokenUpdateRequest) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// 1. Separate admin and user tokens from the request
		var adminUpdate *models.TokenUpdateRequest
		var userUpdates []*models.TokenUpdateRequest
		for _, u := range updates {
			if u.IsAdmin {
				adminUpdate = u
			} else {
				userUpdates = append(userUpdates, u)
			}
		}

		// 2. Handle Admin Token Update
		if adminUpdate != nil && adminUpdate.Token != "" {
			encryptedToken, err := r.crypto.Encrypt(adminUpdate.Token)
			if err != nil {
				return fmt.Errorf("failed to encrypt admin token: %w", err)
			}
			hash := sha256.Sum256([]byte(adminUpdate.Token))
			tokenHash := hex.EncodeToString(hash[:])

			// Update both encrypted value and the hash
			updateData := map[string]interface{}{
				"encrypted_token": encryptedToken,
				"token_hash":      tokenHash,
			}
			if err := tx.Model(&models.AuthToken{}).Where("is_admin = ?", true).Updates(updateData).Error; err != nil {
				return fmt.Errorf("failed to update admin token in db: %w", err)
			}
		}

		// 3. Handle User Tokens Upsert
		var existingTokens []*models.AuthToken
		if err := tx.Where("is_admin = ?", false).Find(&existingTokens).Error; err != nil {
			return fmt.Errorf("failed to fetch existing user tokens: %w", err)
		}
		existingTokenMap := make(map[uint]bool)
		for _, t := range existingTokens {
			existingTokenMap[t.ID] = true
		}

		var tokensToUpsert []models.AuthToken
		for _, req := range userUpdates {
			if req.Token == "" {
				continue // Skip tokens with empty values
			}
			encryptedToken, err := r.crypto.Encrypt(req.Token)
			if err != nil {
				return fmt.Errorf("failed to encrypt token for upsert (ID: %d): %w", req.ID, err)
			}
			hash := sha256.Sum256([]byte(req.Token))
			tokenHash := hex.EncodeToString(hash[:])

			var groups []*models.KeyGroup
			if len(req.AllowedGroupIDs) > 0 {
				if err := tx.Find(&groups, req.AllowedGroupIDs).Error; err != nil {
					return fmt.Errorf("failed to find key groups for token %d: %w", req.ID, err)
				}
			}
			tokensToUpsert = append(tokensToUpsert, models.AuthToken{
				ID:             req.ID,
				EncryptedToken: encryptedToken,
				TokenHash:      tokenHash,
				Description:    req.Description,
				Tag:            req.Tag,
				Status:         req.Status,
				IsAdmin:        false,
				AllowedGroups:  groups,
			})
		}
		if len(tokensToUpsert) > 0 {
			if err := tx.Save(&tokensToUpsert).Error; err != nil {
				return fmt.Errorf("failed to upsert user tokens: %w", err)
			}
		}

		// 4. Handle Deletions
		incomingUserTokenIDs := make(map[uint]bool)
		for _, u := range userUpdates {
			if u.ID != 0 {
				incomingUserTokenIDs[u.ID] = true
			}
		}
		var idsToDelete []uint
		for id := range existingTokenMap {
			if !incomingUserTokenIDs[id] {
				idsToDelete = append(idsToDelete, id)
			}
		}
		if len(idsToDelete) > 0 {
			if err := tx.Model(&models.AuthToken{}).Where("id IN ?", idsToDelete).Association("AllowedGroups").Clear(); err != nil {
				return fmt.Errorf("failed to clear associations for tokens to be deleted: %w", err)
			}
			if err := tx.Where("id IN ?", idsToDelete).Delete(&models.AuthToken{}).Error; err != nil {
				return fmt.Errorf("failed to delete user tokens: %w", err)
			}
		}
		return nil
	})
}

// --- Crypto Helper Functions ---

func (r *gormAuthTokenRepository) decryptToken(token *models.AuthToken) error {
	if token == nil || token.EncryptedToken == "" || token.Token != "" {
		return nil // Nothing to decrypt or already done
	}
	plaintext, err := r.crypto.Decrypt(token.EncryptedToken)
	if err != nil {
		return fmt.Errorf("failed to decrypt auth token ID %d: %w", token.ID, err)
	}
	token.Token = plaintext
	return nil
}

func (r *gormAuthTokenRepository) decryptTokens(tokens []*models.AuthToken) error {
	for i := range tokens {
		if err := r.decryptToken(tokens[i]); err != nil {
			r.logger.Error(err) // Log error but continue for other tokens
		}
	}
	return nil
}

// GetTokenByHashedValue finds a token by its SHA256 hash for authentication.
func (r *gormAuthTokenRepository) GetTokenByHashedValue(tokenHash string) (*models.AuthToken, error) {
	var authToken models.AuthToken
	// Find the active token by its hash. This is the core of our secure authentication.
	err := r.db.Where("token_hash = ? AND status = 'active'", tokenHash).Preload("AllowedGroups").First(&authToken).Error
	if err != nil {
		return nil, err
	}
	// [CRITICAL] Decrypt the token before returning it to the service layer.
	// This ensures that subsequent logic (like in ResourceService) gets the full, usable object.
	if err := r.decryptToken(&authToken); err != nil {
		return nil, err
	}
	return &authToken, nil
}

// SeedAdminToken is a special-purpose function for the seeder to insert the initial admin token.
func (r *gormAuthTokenRepository) SeedAdminToken(encryptedToken, tokenHash string) error {
	adminToken := models.AuthToken{
		EncryptedToken: encryptedToken,
		TokenHash:      tokenHash,
		Description:    "Default Administrator Token",
		Tag:            "SYSTEM_ADMIN",
		IsAdmin:        true,
		Status:         "active", // Ensure the seeded token is active
	}
	// Using FirstOrCreate to be idempotent. If an admin token already exists, it does nothing.
	return r.db.Where(models.AuthToken{IsAdmin: true}).FirstOrCreate(&adminToken).Error
}
