package identity

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func (s *Service) CleanupExpiredVerificationMaterials(ctx context.Context, limit int) (map[string]any, error) {
	if limit <= 0 {
		limit = 200
	}
	materialURLs := []string{}
	cleaned := 0
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var records []UserVerificationRecord
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("material_cleaned = ? AND material_expires_at IS NOT NULL AND material_expires_at <= ?", false, time.Now().UTC()).Order("id ASC").Limit(limit).Find(&records).Error; err != nil {
			return err
		}
		for _, record := range records {
			for _, value := range []*string{record.IDCardFrontImage, record.IDCardBackImage, record.IDCardFaceImage} {
				if value != nil && strings.TrimSpace(*value) != "" {
					materialURLs = append(materialURLs, strings.TrimSpace(*value))
				}
			}
			userUpdates := map[string]any{}
			if record.IDCardFrontImage != nil {
				userUpdates["id_card_front_image"] = nil
			}
			if record.IDCardBackImage != nil {
				userUpdates["id_card_back_image"] = nil
			}
			if record.IDCardFaceImage != nil {
				userUpdates["id_card_face_image"] = nil
			}
			if len(userUpdates) > 0 {
				if err := tx.Model(&User{}).Where("id = ?", record.UserID).Updates(userUpdates).Error; err != nil {
					return err
				}
			}
			recordUpdates := map[string]any{"id_card_front_image": nil, "id_card_back_image": nil, "id_card_face_image": nil, "material_cleaned": true}
			if record.Status == "verified" || record.Status == "rejected" {
				recordUpdates["real_name"] = nil
				recordUpdates["id_card_no"] = nil
				recordUpdates["id_card_no_hash"] = nil
			}
			if err := tx.Model(&UserVerificationRecord{}).Where("id = ?", record.ID).Updates(recordUpdates).Error; err != nil {
				return err
			}
			cleaned++
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	deleted := 0
	for _, materialURL := range materialURLs {
		path, ok := verificationMaterialPath(materialURL)
		if !ok {
			continue
		}
		if err := os.Remove(path); err == nil {
			deleted++
		} else if !os.IsNotExist(err) && s.logger != nil {
			s.logger.Warn("verification material cleanup failed", "path", path, "error", err)
		}
	}
	return map[string]any{"cleaned_records": cleaned, "deleted_files": deleted}, nil
}

func verificationMaterialPath(materialURL string) (string, bool) {
	value := strings.TrimSpace(strings.ReplaceAll(materialURL, `\`, "/"))
	if !strings.HasPrefix(value, "/media/") {
		return "", false
	}
	relative := strings.TrimPrefix(value, "/media/")
	clean := filepath.Clean(filepath.FromSlash(relative))
	if clean == "." || clean == ".." || strings.HasPrefix(filepath.ToSlash(clean), "../") || filepath.IsAbs(clean) {
		return "", false
	}
	for _, rootCandidate := range []string{"media", filepath.Join("..", "media")} {
		root, err := filepath.Abs(rootCandidate)
		if err != nil {
			continue
		}
		path, err := filepath.Abs(filepath.Join(root, clean))
		if err != nil {
			continue
		}
		prefix := root + string(os.PathSeparator)
		if path != root && strings.HasPrefix(path, prefix) {
			return path, true
		}
	}
	return "", false
}
