package domain

import (
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/lib/pq"
	"gorm.io/gorm"
)

// User roles
const (
	RoleSuperAdmin  = "super_admin"  // 시스템 전역 관리자
	RoleTenantAdmin = "tenant_admin" // 테넌트 관리자
	RoleRPAdmin     = "rp_admin"     // 특정 앱(RP) 관리자
	RoleUser        = "user"         // 일반 사용자
)

// User statuses
const (
	UserStatusActive         = "active"
	UserStatusSuspended      = "suspended"
	UserStatusTemporaryLeave = "temporary_leave"
	UserStatusPreboarding    = "preboarding"
	UserStatusBaronGuest     = "baron_guest"
	UserStatusExtendedLeave  = "extended_leave"
	UserStatusArchived       = "archived"
)

func NormalizeUserStatus(status string) string {
	switch strings.ToLower(strings.TrimSpace(status)) {
	case "", UserStatusActive:
		return UserStatusActive
	case "blocked", UserStatusSuspended:
		return UserStatusSuspended
	case "inactive", UserStatusPreboarding:
		return UserStatusPreboarding
	case "leave_of_absence", UserStatusTemporaryLeave:
		return UserStatusTemporaryLeave
	case "baron_only", UserStatusBaronGuest:
		return UserStatusBaronGuest
	case UserStatusExtendedLeave:
		return UserStatusExtendedLeave
	case UserStatusArchived:
		return UserStatusArchived
	default:
		return strings.ToLower(strings.TrimSpace(status))
	}
}

func IsBaronActivityAllowedStatus(status string) bool {
	switch NormalizeUserStatus(status) {
	case UserStatusActive, UserStatusTemporaryLeave, UserStatusBaronGuest:
		return true
	default:
		return false
	}
}

func IsOrgVisibleUserStatus(status string) bool {
	switch NormalizeUserStatus(status) {
	case UserStatusActive, UserStatusTemporaryLeave, UserStatusSuspended:
		return true
	default:
		return false
	}
}

func IsWorksProvisionedUserStatus(status string) bool {
	switch NormalizeUserStatus(status) {
	case UserStatusActive, UserStatusTemporaryLeave, UserStatusSuspended:
		return true
	default:
		return false
	}
}

func IsWorksDeprovisionUserStatus(status string) bool {
	switch NormalizeUserStatus(status) {
	case UserStatusBaronGuest, UserStatusExtendedLeave, UserStatusArchived:
		return true
	default:
		return false
	}
}

// NormalizeRole maps legacy/synonym role values to canonical role keys.
func NormalizeRole(role string) string {
	if normalized, ok := NormalizeRoleAlias(role); ok {
		return normalized
	}
	return RoleUser
}

func NormalizeRoleAlias(role string) (string, bool) {
	normalized := strings.ToLower(strings.TrimSpace(role))
	switch normalized {
	case RoleSuperAdmin, RoleTenantAdmin, RoleRPAdmin, RoleUser:
		return normalized, true
	case "tenant_member", "member":
		return RoleUser, true
	case "admin", "tenantadmin", "tenant-admin":
		return RoleTenantAdmin, true
	case "superadmin", "super-admin":
		return RoleSuperAdmin, true
	default:
		return "", false
	}
}

// User represents the user model stored in PostgreSQL
type User struct {
	ID              string         `gorm:"primaryKey;type:uuid;default:gen_random_uuid()" json:"id"`
	Email           string         `gorm:"uniqueIndex;not null" json:"email"`
	PasswordHash    *string        `gorm:"column:password_hash" json:"-"`
	Name            string         `gorm:"column:name;not null" json:"name"`
	Phone           string         `gorm:"column:phone" json:"phone"`
	Role            string         `gorm:"column:role;default:'user';not null" json:"role"` // super_admin, tenant_admin, rp_admin, user
	AffiliationType string         `gorm:"column:affiliation_type" json:"affiliationType"`
	CompanyCode     string         `gorm:"-" json:"companyCode,omitempty"`
	CompanyCodes    pq.StringArray `gorm:"-" json:"companyCodes,omitempty"`
	TenantID        *string        `gorm:"column:tenant_id;type:uuid;index" json:"tenantId,omitempty"`
	Tenant          *Tenant        `gorm:"foreignKey:TenantID" json:"tenant,omitempty"`
	RelyingPartyID  *string        `gorm:"column:relying_party_id;type:uuid;index" json:"relyingPartyId,omitempty"` // RP Admin용
	Department      string         `gorm:"column:department" json:"department"`
	Grade           string         `gorm:"column:grade" json:"grade"`        // 직급 (예: 수석, 책임, 선임)
	Position        string         `gorm:"column:position" json:"position"`  // 직책 (예: 팀장, 센터장)
	JobTitle        string         `gorm:"column:job_title" json:"jobTitle"` // 직무 (예: 프론트엔드 개발, 기획)
	Metadata        JSONMap        `gorm:"column:metadata;type:jsonb" json:"metadata,omitempty"`
	Status          string         `gorm:"column:status;default:'active'" json:"status"`
	CreatedAt       time.Time      `gorm:"column:created_at" json:"createdAt"`
	UpdatedAt       time.Time      `gorm:"column:updated_at" json:"updatedAt"`
	DeletedAt       gorm.DeletedAt `gorm:"column:deleted_at;index" json:"-"`

	// Multiple identifiers support
	UserLoginIDs []UserLoginID `gorm:"foreignKey:UserID" json:"userLoginIds,omitempty"`
}

// UserLoginID represents multiple custom identifiers for a user
type UserLoginID struct {
	ID       string `gorm:"primaryKey;type:uuid;default:gen_random_uuid()" json:"id"`
	UserID   string `gorm:"type:uuid;not null;index" json:"userId"`
	TenantID string `gorm:"type:uuid;not null;index" json:"tenantId"` // 발급 테넌트
	FieldKey string `gorm:"not null" json:"fieldKey"`                 // 스키마 필드 키 (예: emp_id)
	LoginID  string `gorm:"uniqueIndex;not null" json:"loginId"`      // 실제 값 (예: EMP001)
}

// BeforeCreate hook to generate UUID if not present
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
	if u.ID == "" {
		u.ID = uuid.New().String()
	}
	return
}

// ValidateLoginID checks if the loginID violates any collision, length, or security rules.
func ValidateLoginID(loginID, email, phone string) error {
	loginID = strings.TrimSpace(loginID)
	if loginID == "" {
		return nil
	}

	if len(loginID) < 4 || len(loginID) > 30 {
		return fmt.Errorf("ID must be between 4 and 30 characters")
	}

	if strings.Contains(loginID, "@") {
		return fmt.Errorf("ID cannot be an email format")
	}

	if email != "" && strings.EqualFold(loginID, email) {
		return fmt.Errorf("ID cannot be the same as the email address")
	}

	if phone != "" {
		normalizedPhone := strings.ReplaceAll(phone, "-", "")
		normalizedPhone = strings.ReplaceAll(normalizedPhone, " ", "")
		if strings.HasPrefix(normalizedPhone, "010") {
			normalizedPhone = "+82" + normalizedPhone[1:]
		} else if strings.HasPrefix(normalizedPhone, "82") {
			normalizedPhone = "+" + normalizedPhone
		}

		if loginID == phone || loginID == normalizedPhone {
			return fmt.Errorf("ID cannot be the same as the phone number")
		}
	}

	isPureNumber := true
	loginIDDigits := strings.ReplaceAll(loginID, "-", "")
	loginIDDigits = strings.ReplaceAll(loginIDDigits, " ", "")
	for _, c := range loginIDDigits {
		if (c < '0' || c > '9') && c != '+' {
			isPureNumber = false
			break
		}
	}

	if isPureNumber && len(loginIDDigits) >= 10 && len(loginIDDigits) <= 12 {
		if strings.HasPrefix(loginIDDigits, "010") || strings.HasPrefix(loginIDDigits, "82") || strings.HasPrefix(loginIDDigits, "+82") {
			return fmt.Errorf("ID cannot be a phone number format")
		}
	}

	reserved := []string{"admin", "system", "root", "master", "superuser", "guest", "operator"}
	lowerID := strings.ToLower(loginID)
	for _, r := range reserved {
		if lowerID == r {
			return fmt.Errorf("reserved ID cannot be used")
		}
	}

	return nil
}
