package services

import (
	"context"
	"fmt"
	"os"
	"strings"

	"github.com/nexuscrm/backend/internal/domain/ports"
	"github.com/nexuscrm/backend/internal/features/records"
	pkgerrors "github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"golang.org/x/crypto/bcrypt"
)

// SystemManager handles system-level operations
type SystemManager struct {
	persistence *records.PersistenceService
	repo        ports.SystemRepository
}

// NewSystemManager creates a new SystemManager
func NewSystemManager(persistence *records.PersistenceService, repo ports.SystemRepository) *SystemManager {
	return &SystemManager{
		persistence: persistence,
		repo:        repo,
	}
}

// GetConfig retrieves a system configuration value
func (sm *SystemManager) GetConfig(ctx context.Context, key string) (*string, error) {
	return sm.repo.GetConfig(ctx, key)
}

// SetConfig sets a system configuration value
func (sm *SystemManager) SetConfig(ctx context.Context, key string, value string, isSecret bool, description string) error {
	config := models.SystemConfig{
		KeyName:     key,
		Value:       value,
		IsSecret:    isSecret,
		Description: &description,
	}

	if err := sm.repo.UpsertConfig(ctx, &config); err != nil {
		return pkgerrors.NewInternalError(fmt.Sprintf("failed to upsert config %s", key), err)
	}
	return nil
}

// DeleteConfig deletes a system configuration if it exists
func (sm *SystemManager) DeleteConfig(ctx context.Context, key string) error {
	if err := sm.repo.DeleteConfig(ctx, key); err != nil {
		return pkgerrors.NewInternalError(fmt.Sprintf("failed to delete config %s", key), err)
	}
	return nil
}

// UpsertProfile creates or updates a system profile
func (sm *SystemManager) UpsertProfile(ctx context.Context, id, name, description string, isSystem bool) error {
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "SystemManager.UpsertProfile")

	profile := models.Profile{
		ID:          id,
		Name:        name,
		Description: &description,
		IsSystem:    isSystem,
		// SF-faithful license; must be set explicitly because ToSObject marshals
		// the zero-value "" and would overwrite the DB default 'Standard' on the
		// UPDATE branch below. All bootstrap profiles are Standard-license in SF.
		UserType: "Standard",
	}

	// Check existence by Name (Unique Key) or ID
	existingID, err := sm.repo.GetProfileIDByName(ctx, name)
	if err != nil {
		return pkgerrors.NewInternalError("failed to check profile existence", err)
	}

	if existingID != "" {
		// Update
		profile.ID = existingID
		if err := sm.persistence.Update(ctx, constants.TableProfile, existingID, profile.ToSObject(), nil); err != nil {
			return pkgerrors.NewInternalError(fmt.Sprintf("failed to update profile %s", name), err)
		}
	} else {
		// Insert
		if _, err := sm.persistence.Insert(ctx, constants.TableProfile, profile.ToSObject(), nil); err != nil {
			return pkgerrors.NewInternalError(fmt.Sprintf("failed to insert profile %s", name), err)
		}
	}
	return nil
}

// UpsertUser creates or updates a system user
func (sm *SystemManager) UpsertUser(ctx context.Context, id, name, email, password, profileID string) error {
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return pkgerrors.NewInternalError(fmt.Sprintf("failed to hash password for user %s", email), err)
	}

	var firstName, lastName string
	// Simple Name Split - use full name as first name if single word
	parts := strings.SplitN(name, " ", 2)
	firstName = parts[0]
	if len(parts) > 1 {
		lastName = parts[1]
	}

	username := email // Default username to email

	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "SystemManager.UpsertUser")

	// Prepare data struct
	user := models.User{
		ID:        id,
		Username:  username,
		Email:     email,
		Password:  string(hashedPassword),
		FirstName: firstName,
		LastName:  lastName,
		ProfileID: profileID,
		IsActive:  true,
		// CreatedDate handles by Persistence default or set here?
		// Persistence handles created_date if missing? It populates system fields.
	}

	// Check existence by Email (Unique Key)
	existingID, err := sm.repo.GetUserIDByEmail(ctx, email)
	if err != nil {
		return pkgerrors.NewInternalError("failed to check user existence", err)
	}

	if existingID != "" {
		// Update
		user.ID = existingID // Force ID to match existing
		if err := sm.persistence.Update(ctx, constants.TableUser, existingID, user.ToSObject(), nil); err != nil {
			return pkgerrors.NewInternalError(fmt.Sprintf("failed to update user %s", email), err)
		}
	} else {
		// Insert
		if _, err := sm.persistence.Insert(ctx, constants.TableUser, user.ToSObject(), nil); err != nil {
			return pkgerrors.NewInternalError(fmt.Sprintf("failed to insert user %s", email), err)
		}
	}
	return nil
}

// ForceSetUserPassword hashes and force-updates a user's password.
// Used after batch upsert to ensure explicitly configured passwords take effect.
func (sm *SystemManager) ForceSetUserPassword(ctx context.Context, email, password string) error {
	h, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return pkgerrors.NewInternalError("failed to hash password for force update", err)
	}
	return sm.repo.ForceUpdateUserPassword(ctx, email, string(h))
}

// BatchUpsertProfiles inserts multiple profiles using direct SQL for performance during bootstrap
func (sm *SystemManager) BatchUpsertProfiles(ctx context.Context, profiles []models.Profile) error {
	return sm.repo.BatchUpsertProfiles(ctx, profiles)
}

// RenameDuplicateProfilesByName renames SF-migrated profiles that collide with bootstrap profile names.
func (sm *SystemManager) RenameDuplicateProfilesByName(ctx context.Context, bootstrapID, currentName, newName string) (int64, error) {
	return sm.repo.RenameDuplicateProfilesByName(ctx, bootstrapID, currentName, newName)
}

// BatchUpsertUsers inserts multiple users using direct SQL for performance during bootstrap
// Uses a dynamic cache for password hashing to avoid redundant BCrypt calls for identical passwords
func (sm *SystemManager) BatchUpsertUsers(ctx context.Context, users []models.User) error {
	if len(users) == 0 {
		return nil
	}

	hashCache := make(map[string]string)

	preparedUsers := make([]models.User, len(users))

	for i, u := range users {
		hashedPwd, ok := hashCache[u.Password]
		if !ok {
			h, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
			if err != nil {
				return pkgerrors.NewInternalError("failed to hash password", err)
			}
			hashedPwd = string(h)
			hashCache[u.Password] = hashedPwd
		}

		// Calculate Names
		// Caller (ProvisionBootstrapIdentity) is responsible for splitting Name into FirstName/LastName
		// stored in u.FirstName and u.LastName

		// Create a copy with hashed password
		preparedUser := u
		preparedUser.Password = hashedPwd
		preparedUsers[i] = preparedUser
	}

	return sm.repo.BatchUpsertUsers(ctx, preparedUsers)
}
func (sm *SystemManager) UpsertConnectedApp(ctx context.Context, id, name, consumerKey, consumerSecret string) error {
	ctx = constants.WithSecurityContext(ctx, constants.SystemContext, "SystemManager.UpsertConnectedApp")

	app := models.SObject{
		constants.FieldID:             id,
		constants.FieldName:           name,
		constants.FieldConsumerKey:    consumerKey,
		constants.FieldConsumerSecret: consumerSecret,
		constants.FieldIsActive:       true,
		constants.FieldCallbackURL:    os.Getenv("API_BASE_URL") + "/callback",
	}

	// Upsert: try Update first, then Insert if not found
	err := sm.persistence.Update(ctx, constants.TableConnectedApp, id, app, nil)
	if err == nil {
		return nil
	}

	// If update failed (likely not found), Insert
	_, err = sm.persistence.Insert(ctx, constants.TableConnectedApp, app, nil)
	return err
}
