package service

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/repository"
	"context"
	"fmt"
	"strings"
	"time"
)

type UserProjectionSyncService struct {
	kratos KratosAdminService
	repo   repository.UserProjectionRepository
}

type UserProjectionReconciler interface {
	Reconcile(ctx context.Context) (int, error)
}

func NewUserProjectionSyncService(kratos KratosAdminService, repo repository.UserProjectionRepository) *UserProjectionSyncService {
	return &UserProjectionSyncService{
		kratos: kratos,
		repo:   repo,
	}
}

func (s *UserProjectionSyncService) Reconcile(ctx context.Context) (int, error) {
	if s == nil || s.kratos == nil || s.repo == nil {
		return 0, fmt.Errorf("user projection sync dependencies are not configured")
	}

	identities, err := s.kratos.ListIdentities(ctx)
	if err != nil {
		_ = s.repo.MarkFailed(ctx, err)
		return 0, err
	}

	users := make([]domain.User, 0, len(identities))
	for _, identity := range identities {
		users = append(users, MapKratosIdentityToLocalUser(identity))
	}
	if err := s.repo.ReplaceAllFromKratos(ctx, users); err != nil {
		_ = s.repo.MarkFailed(ctx, err)
		return 0, err
	}
	return len(users), nil
}

func MapKratosIdentityToLocalUser(identity KratosIdentity) domain.User {
	traits := identity.Traits
	now := time.Now()
	createdAt := identity.CreatedAt
	if createdAt.IsZero() {
		createdAt = now
	}
	updatedAt := identity.UpdatedAt
	if updatedAt.IsZero() {
		updatedAt = now
	}

	role, ok := domain.NormalizeRoleAlias(kratosProjectionTraitString(traits, "role"))
	if !ok {
		role, ok = domain.NormalizeRoleAlias(kratosProjectionTraitString(traits, "grade"))
		if !ok {
			role = domain.RoleUser
		}
	}
	grade := kratosProjectionTraitString(traits, "grade")
	if _, ok := domain.NormalizeRoleAlias(grade); ok {
		grade = ""
	}

	user := domain.User{
		ID:              identity.ID,
		Email:           kratosProjectionTraitString(traits, "email"),
		Name:            kratosProjectionTraitString(traits, "name"),
		Phone:           domain.NormalizePhoneNumber(kratosProjectionTraitString(traits, "phone_number")),
		Role:            role,
		Status:          normalizeProjectionStatus(identity.State),
		Department:      kratosProjectionTraitString(traits, "department"),
		Grade:           grade,
		Position:        kratosProjectionTraitString(traits, "position"),
		JobTitle:        kratosProjectionTraitString(traits, "jobTitle"),
		AffiliationType: kratosProjectionTraitString(traits, "affiliationType"),
		CreatedAt:       createdAt,
		UpdatedAt:       updatedAt,
		Metadata:        make(domain.JSONMap),
	}
	if tenantID := kratosProjectionTraitString(traits, "tenant_id"); tenantID != "" {
		user.TenantID = &tenantID
	}
	if relyingPartyID := kratosProjectionTraitString(traits, "relying_party_id"); relyingPartyID != "" {
		user.RelyingPartyID = &relyingPartyID
	}

	coreTraits := map[string]bool{
		"email": true, "name": true, "phone_number": true,
		"grade": true, "role": true,
		"companyCode": true, "company_code": true, "companyCodes": true,
		"tenant_id": true, "department": true,
		"position": true, "jobTitle": true, "affiliationType": true,
		"relying_party_id": true, "custom_login_ids": true, "id": true,
	}
	for key, value := range traits {
		if !coreTraits[key] {
			user.Metadata[key] = value
		}
	}
	return user
}

func kratosProjectionTraitString(traits map[string]any, key string) string {
	if traits == nil {
		return ""
	}
	value, ok := traits[key]
	if !ok || value == nil {
		return ""
	}
	if str, ok := value.(string); ok {
		return str
	}
	return fmt.Sprint(value)
}

func kratosProjectionTraitStringArray(traits map[string]any, key string) []string {
	if traits == nil {
		return nil
	}
	switch value := traits[key].(type) {
	case []string:
		return value
	case []any:
		items := make([]string, 0, len(value))
		for _, item := range value {
			if str, ok := item.(string); ok && strings.TrimSpace(str) != "" {
				items = append(items, str)
			}
		}
		return items
	default:
		return nil
	}
}

func normalizeProjectionStatus(state string) string {
	normalized := domain.NormalizeUserStatus(state)
	if normalized == "" {
		return domain.UserStatusActive
	}
	return normalized
}
