package repository

import (
	"context"
	"errors"
	"time"

	"gorm.io/gorm"

	"security-center-backend/internal/model"
)

// ErrAPIKeyNotFoundOrNotOwned is returned by Revoke when no row matches both the
// key id and the owner: the key does not exist or belongs to another user.
var ErrAPIKeyNotFoundOrNotOwned = errors.New("api key not found or not owned by user")

// ListAPIKeysFilter is a placeholder for the admin list query (Phase 2).
type ListAPIKeysFilter struct {
	Page        int
	PageSize    int
	OwnerUserID *uint64
	Status      string
}

type APIKeyRepository interface {
	Create(ctx context.Context, key *model.APIKey) error
	// FindByHash returns (nil, nil) when no key matches the hash.
	FindByHash(ctx context.Context, hash string) (*model.APIKey, error)
	TouchLastUsed(ctx context.Context, id uint64) error

	// —— 以下方法 Phase 2 (P2-T2) 实现 ——
	FindByIDForOwner(ctx context.Context, id, ownerUserID uint64) (*model.APIKey, error)
	ListByOwner(ctx context.Context, ownerUserID uint64) ([]*model.APIKey, error)
	Revoke(ctx context.Context, id, ownerUserID uint64) error
	// FindByID is the admin fallback without owner scoping.
	FindByID(ctx context.Context, id uint64) (*model.APIKey, error)
	// ListAll is the admin fallback without owner scoping.
	ListAll(ctx context.Context, filter ListAPIKeysFilter) ([]*model.APIKey, error)
}

type GormAPIKeyRepository struct {
	db *gorm.DB
}

func NewAPIKeyRepository(db *gorm.DB) APIKeyRepository {
	return &GormAPIKeyRepository{db: db}
}

func (r *GormAPIKeyRepository) Create(ctx context.Context, key *model.APIKey) error {
	return r.db.WithContext(ctx).Create(key).Error
}

func (r *GormAPIKeyRepository) FindByHash(ctx context.Context, hash string) (*model.APIKey, error) {
	var key model.APIKey
	// Preload Owner: AgentAuth 需校验主人状态 (plan 003 §5.2①), 单关联查询代价极小。
	if err := r.db.WithContext(ctx).Preload("Owner").Where("key_hash = ?", hash).First(&key).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &key, nil
}

func (r *GormAPIKeyRepository) TouchLastUsed(ctx context.Context, id uint64) error {
	return r.db.WithContext(ctx).Model(&model.APIKey{}).
		Where("id = ?", id).
		Update("last_used_at", time.Now()).Error
}

func (r *GormAPIKeyRepository) FindByIDForOwner(ctx context.Context, id, ownerUserID uint64) (*model.APIKey, error) {
	var key model.APIKey
	if err := r.db.WithContext(ctx).
		Preload("Owner").
		Where("id = ? AND owner_user_id = ?", id, ownerUserID).
		First(&key).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &key, nil
}

func (r *GormAPIKeyRepository) ListByOwner(ctx context.Context, ownerUserID uint64) ([]*model.APIKey, error) {
	var keys []*model.APIKey
	if err := r.db.WithContext(ctx).
		Preload("Owner").
		Where("owner_user_id = ?", ownerUserID).
		Order("id DESC").
		Find(&keys).Error; err != nil {
		return nil, err
	}
	return keys, nil
}

func (r *GormAPIKeyRepository) Revoke(ctx context.Context, id, ownerUserID uint64) error {
	res := r.db.WithContext(ctx).Model(&model.APIKey{}).
		Where("id = ? AND owner_user_id = ?", id, ownerUserID).
		Update("status", model.APIKeyStatusRevoked)
	if res.Error != nil {
		return res.Error
	}
	if res.RowsAffected == 0 {
		return ErrAPIKeyNotFoundOrNotOwned
	}
	return nil
}

func (r *GormAPIKeyRepository) FindByID(ctx context.Context, id uint64) (*model.APIKey, error) {
	var key model.APIKey
	if err := r.db.WithContext(ctx).Preload("Owner").First(&key, id).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &key, nil
}

func (r *GormAPIKeyRepository) ListAll(ctx context.Context, filter ListAPIKeysFilter) ([]*model.APIKey, error) {
	page := filter.Page
	if page < 1 {
		page = 1
	}
	pageSize := filter.PageSize
	if pageSize <= 0 {
		pageSize = 20
	}

	query := r.db.WithContext(ctx).Model(&model.APIKey{}).Preload("Owner")
	if filter.OwnerUserID != nil {
		query = query.Where("owner_user_id = ?", *filter.OwnerUserID)
	}
	if filter.Status != "" {
		query = query.Where("status = ?", filter.Status)
	}

	var keys []*model.APIKey
	if err := query.Order("id DESC").
		Offset((page - 1) * pageSize).
		Limit(pageSize).
		Find(&keys).Error; err != nil {
		return nil, err
	}
	return keys, nil
}
