package bootstrap

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/repository"
	"context"
	"errors"
	"fmt"
	"net/mail"
	"strings"
	"time"

	"gorm.io/gorm"
)

type SuperAdminIdentityAdmin interface {
	FindIdentityIDByIdentifier(ctx context.Context, identifier string) (string, error)
	CreateUser(ctx context.Context, user *domain.BrokerUser, password string) (string, error)
	UpdateIdentityPassword(ctx context.Context, identityID, newPassword string) error
}

type SuperAdminStore interface {
	FindUserByEmail(ctx context.Context, email string) (*domain.User, error)
	CreateUser(ctx context.Context, user *domain.User) error
	UpdateUserSuperAdmin(ctx context.Context, userID string, name string) (*domain.User, error)
	EnqueueSuperAdminRelation(ctx context.Context, userID string) error
}

type EnsureSuperAdminOptions struct {
	Email          string
	Password       string
	Name           string
	Source         string
	UpdatePassword bool
}

type EnsureSuperAdminResult struct {
	Email              string
	IdentityID         string
	LocalUserID        string
	IdentityCreated    bool
	PasswordUpdated    bool
	LocalUserCreated   bool
	LocalUserUpdated   bool
	KetoRelationQueued bool
}

func EnsureSuperAdmin(ctx context.Context, identityAdmin SuperAdminIdentityAdmin, store SuperAdminStore, opts EnsureSuperAdminOptions) (EnsureSuperAdminResult, error) {
	email := strings.ToLower(strings.TrimSpace(opts.Email))
	name := strings.TrimSpace(opts.Name)
	if name == "" {
		name = "System Admin"
	}
	source := strings.TrimSpace(opts.Source)
	if source == "" {
		source = "admin_cli"
	}
	result := EnsureSuperAdminResult{Email: email}

	if _, err := mail.ParseAddress(email); err != nil {
		return result, fmt.Errorf("invalid admin email: %w", err)
	}
	if identityAdmin == nil {
		return result, errors.New("identity admin is required")
	}
	if store == nil {
		return result, errors.New("super admin store is required")
	}

	identityID, err := identityAdmin.FindIdentityIDByIdentifier(ctx, email)
	if err != nil {
		return result, fmt.Errorf("find admin identity: %w", err)
	}
	if identityID == "" {
		if strings.TrimSpace(opts.Password) == "" {
			return result, errors.New("admin password is required to create identity")
		}
		identityID, err = identityAdmin.CreateUser(ctx, buildSuperAdminBrokerUser(email, name), opts.Password)
		if err != nil {
			return result, fmt.Errorf("create admin identity: %w", err)
		}
		result.IdentityCreated = true
	} else if opts.UpdatePassword {
		if strings.TrimSpace(opts.Password) == "" {
			return result, errors.New("admin password is required to update identity password")
		}
		if err := identityAdmin.UpdateIdentityPassword(ctx, identityID, opts.Password); err != nil {
			return result, fmt.Errorf("update admin identity password: %w", err)
		}
		result.PasswordUpdated = true
	}
	result.IdentityID = identityID

	user, err := store.FindUserByEmail(ctx, email)
	if err != nil {
		return result, fmt.Errorf("find local admin user: %w", err)
	}
	if user == nil {
		if identityID == "" {
			return result, errors.New("identity id is required to create local admin user")
		}
		user = &domain.User{
			ID:        identityID,
			Email:     email,
			Name:      name,
			Role:      domain.RoleSuperAdmin,
			Status:    domain.UserStatusActive,
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
			Metadata: domain.JSONMap{
				"source": source,
			},
		}
		if err := store.CreateUser(ctx, user); err != nil {
			return result, fmt.Errorf("create local admin user: %w", err)
		}
		result.LocalUserCreated = true
	} else if domain.NormalizeRole(user.Role) != domain.RoleSuperAdmin || user.Status != domain.UserStatusActive || (name != "" && user.Name != name) {
		user, err = store.UpdateUserSuperAdmin(ctx, user.ID, name)
		if err != nil {
			return result, fmt.Errorf("update local admin user: %w", err)
		}
		result.LocalUserUpdated = true
	}
	result.LocalUserID = user.ID

	if err := store.EnqueueSuperAdminRelation(ctx, user.ID); err != nil {
		return result, fmt.Errorf("enqueue super admin keto relation: %w", err)
	}
	result.KetoRelationQueued = true
	return result, nil
}

func buildSuperAdminBrokerUser(email, name string) *domain.BrokerUser {
	return &domain.BrokerUser{
		Email:       email,
		Name:        name,
		PhoneNumber: "",
		Attributes: map[string]interface{}{
			"department":      "Admin",
			"affiliationType": "internal",
			"grade":           "",
			"role":            domain.RoleSuperAdmin,
		},
	}
}

type gormSuperAdminStore struct {
	db     *gorm.DB
	outbox repository.KetoOutboxRepository
}

func NewGormSuperAdminStore(db *gorm.DB, outbox repository.KetoOutboxRepository) SuperAdminStore {
	return &gormSuperAdminStore{db: db, outbox: outbox}
}

func (s *gormSuperAdminStore) FindUserByEmail(ctx context.Context, email string) (*domain.User, error) {
	var user domain.User
	if err := s.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, err
	}
	return &user, nil
}

func (s *gormSuperAdminStore) CreateUser(ctx context.Context, user *domain.User) error {
	return s.db.WithContext(ctx).Create(user).Error
}

func (s *gormSuperAdminStore) UpdateUserSuperAdmin(ctx context.Context, userID string, name string) (*domain.User, error) {
	updates := map[string]interface{}{
		"role":       domain.RoleSuperAdmin,
		"status":     domain.UserStatusActive,
		"updated_at": time.Now(),
	}
	if strings.TrimSpace(name) != "" {
		updates["name"] = strings.TrimSpace(name)
	}
	if err := s.db.WithContext(ctx).Model(&domain.User{}).Where("id = ?", userID).Updates(updates).Error; err != nil {
		return nil, err
	}

	var user domain.User
	if err := s.db.WithContext(ctx).Where("id = ?", userID).First(&user).Error; err != nil {
		return nil, err
	}
	return &user, nil
}

func (s *gormSuperAdminStore) EnqueueSuperAdminRelation(ctx context.Context, userID string) error {
	if s.outbox == nil {
		return nil
	}
	return s.outbox.Create(ctx, &domain.KetoOutbox{
		Namespace: "System",
		Object:    "global",
		Relation:  "super_admins",
		Subject:   "User:" + userID,
		Action:    domain.KetoOutboxActionCreate,
	})
}
