package mongo

import (
	"context"
	"errors"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	mongodriver "go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"

	"scheduler-backend/internal/store/model"
)

type AdminUserStore struct {
	collection *mongodriver.Collection
}

func NewAdminUserStore(db *mongodriver.Database) *AdminUserStore {
	return &AdminUserStore{
		collection: db.Collection("admin_users"),
	}
}

func (s *AdminUserStore) List(ctx context.Context) ([]model.AdminUser, error) {
	findCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	cursor, err := s.collection.Find(findCtx, bson.D{}, options.Find().SetSort(bson.D{{Key: "updatedAt", Value: -1}}))
	if err != nil {
		return nil, err
	}
	defer cursor.Close(findCtx)

	items := make([]model.AdminUser, 0)
	for cursor.Next(findCtx) {
		var item model.AdminUser
		if err := cursor.Decode(&item); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	if err := cursor.Err(); err != nil {
		return nil, err
	}

	if len(items) == 0 {
		defaultAdmin := model.AdminUser{
			Account:      "admin",
			Nickname:     "调度管理员",
			Status:       "active",
			Role:         "super_admin",
			Remark:       "default admin user",
			PasswordHash: hashPassword(defaultAdminPassword),
		}
		if err := s.Create(ctx, &defaultAdmin); err != nil {
			return nil, err
		}
		items = append(items, defaultAdmin)
	}

	return items, nil
}

func (s *AdminUserStore) GetByID(ctx context.Context, id primitive.ObjectID) (*model.AdminUser, error) {
	findCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	var item model.AdminUser
	err := s.collection.FindOne(findCtx, bson.M{"_id": id}).Decode(&item)
	if err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, nil
		}
		return nil, err
	}
	return &item, nil
}

func (s *AdminUserStore) Create(ctx context.Context, item *model.AdminUser) error {
	insertCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	now := time.Now()
	item.CreatedAt = now
	item.UpdatedAt = now
	if item.Status == "" {
		item.Status = "active"
	}
	if item.Role == "" {
		item.Role = "super_admin"
	}

	result, err := s.collection.InsertOne(insertCtx, item)
	if err != nil {
		return err
	}
	if oid, ok := result.InsertedID.(primitive.ObjectID); ok {
		item.ID = oid
	}
	return nil
}

func (s *AdminUserStore) Update(ctx context.Context, item *model.AdminUser) error {
	updateCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	item.UpdatedAt = time.Now()
	_, err := s.collection.UpdateByID(updateCtx, item.ID, bson.M{
		"$set": bson.M{
			"account":   item.Account,
			"nickname":  item.Nickname,
			"status":    item.Status,
			"role":      item.Role,
			"remark":    item.Remark,
			"updatedAt": item.UpdatedAt,
		},
	})
	return err
}

func (s *AdminUserStore) ChangePassword(ctx context.Context, id primitive.ObjectID, newPassword string) error {
	updateCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	_, err := s.collection.UpdateByID(updateCtx, id, bson.M{
		"$set": bson.M{
			"passwordHash": hashPassword(newPassword),
			"updatedAt":    time.Now(),
		},
	})
	return err
}

func (s *AdminUserStore) Authenticate(ctx context.Context, account, password string) (*model.AdminUser, error) {
	findCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()

	var item model.AdminUser
	err := s.collection.FindOne(findCtx, bson.M{"account": account}).Decode(&item)
	if err != nil {
		if errors.Is(err, mongodriver.ErrNoDocuments) {
			return nil, ErrInvalidPassword
		}
		return nil, err
	}
	if item.Status == "disabled" || item.PasswordHash != hashPassword(password) {
		return nil, ErrInvalidPassword
	}
	return &item, nil
}
