package raft

import (
	"crypto/hmac"
	"crypto/sha1"
	"crypto/sha256"
	"encoding/base32"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"sync"
	"time"

	"igit.com/xbase/raft/db"
)

// ==================== Auth Constants ====================

const (
	ActionRead  = "read"
	ActionWrite = "write" // Includes Set, Del
	ActionAdmin = "admin" // Cluster ops

	SystemKeyPrefix = "system."
	AuthUserPrefix  = "system.user."
	AuthRolePrefix  = "system.role."
	AuthSessionPrefix = "system.session."
	AuthConfigKey   = "system.config"
)

// ==================== Auth Data Structures ====================

// Constraint defines numeric constraints on values
type Constraint struct {
	Min *float64 `json:"min,omitempty"`
	Max *float64 `json:"max,omitempty"`
}

// Permission defines access to a key pattern
type Permission struct {
	KeyPattern string      `json:"key"`     // Prefix match. "*" means all.
	Actions    []string    `json:"actions"` // "read", "write", "admin"
	Constraint *Constraint `json:"constraint,omitempty"`
}

// Role defines a set of permissions
type Role struct {
	Name        string       `json:"name"`
	Permissions []Permission `json:"permissions"`
	ParentRoles []string     `json:"parent_roles,omitempty"` // Inherit permissions from these roles
}

// User defines a system user
type User struct {
	Username     string `json:"username"`
	PasswordHash string `json:"password_hash"` // SHA256(salt + password)
	Salt         string `json:"salt"`
	Roles        []string `json:"roles"`
	
	// Specific overrides
	AllowPermissions []Permission `json:"allow_permissions"`
	DenyPermissions  []Permission `json:"deny_permissions"` // Higher priority

	Email      string `json:"email"`
	Icon       string `json:"icon"`
	IsOnline   bool   `json:"is_online"`
	MFASecret  string `json:"mfa_secret"`  // Base32 encoded TOTP secret
	MFAEnabled bool   `json:"mfa_enabled"`

	WhitelistIPs []string `json:"whitelist_ips"`
	BlacklistIPs []string `json:"blacklist_ips"`
}

// Session represents an active login session
// Modified: Sessions are stored in Raft, but permCache is transient
type Session struct {
	Token    string `json:"token"`
	Username string `json:"username"`
	LoginIP  string `json:"login_ip"`
	Expiry   int64  `json:"expiry"` // Unix timestamp
	
	// Permission Cache: key+action -> bool
	// Used to speed up frequent checks (e.g. loops)
	// Not serialized to JSON (rebuilt on demand)
	permCache sync.Map `json:"-"`
}

// AuthConfig defines global auth settings
type AuthConfig struct {
	Enabled bool `json:"enabled"`
}

// ==================== Auth Manager ====================

// AuthManager handles authentication and authorization
type AuthManager struct {
	server *KVServer
	mu     sync.RWMutex

	// In-memory cache
	users    map[string]*User
	roles    map[string]*Role
	// sessions are now ONLY in-memory (local cache)
	sessions map[string]*Session
	enabled  bool
}

// NewAuthManager creates a new AuthManager
func NewAuthManager(server *KVServer) *AuthManager {
	return &AuthManager{
		server:   server,
		users:    make(map[string]*User),
		roles:    make(map[string]*Role),
		sessions: make(map[string]*Session),
		enabled:  false,
	}
}

// HashPassword hashes a password with salt (Exported for tools)
func HashPassword(password, salt string) string {
	hash := sha256.Sum256([]byte(salt + password))
	return hex.EncodeToString(hash[:])
}

// Internal helper
func hashPassword(password, salt string) string {
	return HashPassword(password, salt)
}

// Helper to generate token
func generateToken() string {
	// In a real system use crypto/rand, but here we use math/rand seeded in main or just simple timestamp mix for example
	// Since we can't easily access crypto/rand without import and "no external lib" usually implies standard lib only,
	// crypto/rand IS standard lib.
	h := sha256.Sum256([]byte(fmt.Sprintf("%d", time.Now().UnixNano())))
	return hex.EncodeToString(h[:])
}

// Helper for TOTP (Google Authenticator)
func validateTOTP(secret string, code string) bool {
	// Decode base32 secret
	key, err := base32.StdEncoding.DecodeString(strings.ToUpper(secret))
	if err != nil {
		return false
	}

	// Current time steps (30s window)
	// Check current, prev, and next window to allow for slight skew
	now := time.Now().Unix()
	return checkTOTP(key, now, code) || checkTOTP(key, now-30, code) || checkTOTP(key, now+30, code)
}

func checkTOTP(key []byte, timestamp int64, code string) bool {
	step := timestamp / 30
	buf := make([]byte, 8)
	binary.BigEndian.PutUint64(buf, uint64(step))

	h := hmac.New(sha1.New, key)
	h.Write(buf)
	sum := h.Sum(nil)

	offset := sum[len(sum)-1] & 0xf
	val := int64(((int(sum[offset]) & 0x7f) << 24) |
		((int(sum[offset+1]) & 0xff) << 16) |
		((int(sum[offset+2]) & 0xff) << 8) |
		(int(sum[offset+3]) & 0xff))

	otp := val % 1000000
	expected := fmt.Sprintf("%06d", otp)
	return expected == code
}

// UpdateCache updates the internal cache based on key-value changes
// Called by the state machine when applying logs
func (am *AuthManager) UpdateCache(key, value string, isDelete bool) {
	am.mu.Lock()
	defer am.mu.Unlock()

	if key == AuthConfigKey {
		if isDelete {
			am.enabled = false
		} else {
			var config AuthConfig
			if err := json.Unmarshal([]byte(value), &config); err == nil {
				am.enabled = config.Enabled
			}
		}
		return
	}

	if strings.HasPrefix(key, AuthUserPrefix) {
		username := strings.TrimPrefix(key, AuthUserPrefix)
		if isDelete {
			delete(am.users, username)
		} else {
			var user User
			if err := json.Unmarshal([]byte(value), &user); err == nil {
				am.users[username] = &user
				am.server.Raft.config.Logger.Info("Updated user cache for %s (hash prefix: %s)", username, user.PasswordHash[:8])
			} else {
				am.server.Raft.config.Logger.Error("Failed to unmarshal user %s: %v", username, err)
			}
		}
		return
	}

	if strings.HasPrefix(key, AuthRolePrefix) {
		rolename := strings.TrimPrefix(key, AuthRolePrefix)
		if isDelete {
			delete(am.roles, rolename)
		} else {
			var role Role
			if err := json.Unmarshal([]byte(value), &role); err == nil {
				am.roles[rolename] = &role
			}
		}
		return
	}

	// Note: AuthSessionPrefix is no longer handled here because sessions are local only.
	if strings.HasPrefix(key, AuthSessionPrefix) {
		token := strings.TrimPrefix(key, AuthSessionPrefix)
		if isDelete {
			delete(am.sessions, token)
		} else {
			var session Session
			if err := json.Unmarshal([]byte(value), &session); err == nil {
				// Check expiry (though normally we'd replicate delete on expiry, but follower can check too)
				if time.Now().Unix() > session.Expiry {
					delete(am.sessions, token)
				} else {
					am.sessions[token] = &session
				}
			}
		}
		return
	}
	
	// Invalidate permission cache on any auth change (Users, Roles, or Sessions changed)
	// Even though session changes are frequent, for safety/simplicity we can clear cache.
	// But optimizing: Session change only affects THAT session.
	// User/Role change affects potentially all sessions.
	// Let's clear cache if User/Role changed.
	// If Session changed (added/removed), CheckPermission will hit/miss am.sessions map anyway.
	// So we don't strictly need to clear permCache for session changes unless we cache existence.
	// We cache (token:key:action -> result).
	// If session is deleted, we should clear its cache entries?
	// The sync.Map doesn't support easy clearing by pattern.
	// Given we want High Performance for search (loops), cache is useful.
	// But if we use "Cluster Shared Session", updates come via Raft.
	// Let's just clear everything on User/Role update.
	// For Session update, we don't clear global cache, relying on GetSession check in CheckPermission.
}

// IsEnabled checks if auth is enabled
func (am *AuthManager) IsEnabled() bool {
	am.mu.RLock()
	defer am.mu.RUnlock()
	return am.enabled
}

// CheckPermission checks if a user has permission to perform an action on a key
func (am *AuthManager) CheckPermission(token string, key string, action string, value string) error {
	am.mu.RLock()
	enabled := am.enabled
	am.mu.RUnlock()

	if !enabled {
		return nil
	}
	
	// Internal system bypass
	if token == "SYSTEM_INTERNAL" {
		return nil
	}

	am.mu.RLock()
	session, ok := am.sessions[token]
	am.mu.RUnlock()

	if !ok {
		return fmt.Errorf("invalid or expired session")
	}

	if time.Now().Unix() > session.Expiry {
		return fmt.Errorf("session expired")
	}

	// Check Session Local Cache
	// Cache key: "action:key" (value is harder to cache due to constraints, but if value is empty/read, we can cache)
	// For writes with value constraints, caching is risky unless we include value in cache key or don't cache constraint checks.
	// For READS (action="read"), value is empty, so we can cache.
	// For WRITES without value (Del), or simple sets, we can cache if no constraints.
	// Let's safe cache only for Read or if we verify constraint logic is stable.
	// To be safe and simple: Cache the result of (key, action). If constraints exist, the checkPerms function will fail if value invalid.
	// But if we cache "true", we skip checkPerms.
	// So we can only cache if the permission granted did NOT depend on value constraints.
	// That's complex.
	// Simplified: Cache only "read" and "admin" actions?
	cacheKey := action + ":" + key
	if action == ActionRead {
		if val, hit := session.permCache.Load(cacheKey); hit {
			if allowed, ok := val.(bool); ok {
				if allowed {
					return nil
				} else {
					return fmt.Errorf("permission denied (cached)")
				}
			}
		}
	}

	am.mu.RLock()
	user, userOk := am.users[session.Username]
	am.mu.RUnlock()

	if !userOk {
		return fmt.Errorf("user not found")
	}

	err := am.checkUserPermission(user, key, action, value)
	
	// Update Cache for Read
	if action == ActionRead {
		session.permCache.Store(cacheKey, err == nil)
	}
	
	return err
}

func (am *AuthManager) checkUserPermission(user *User, key, action, value string) error {
	// 1. Check Deny Permissions (User level)
	for _, perm := range user.DenyPermissions {
		if matchKey(perm.KeyPattern, key) && hasAction(perm.Actions, action) {
			return fmt.Errorf("permission denied (explicit deny)")
		}
	}

	// 2. Check Allow Permissions (User level)
	if checkPerms(user.AllowPermissions, key, action, value) {
		return nil
	}

	// 3. Check Roles
	for _, roleName := range user.Roles {
		if am.checkRole(roleName, key, action, value, make(map[string]bool)) {
			return nil
		}
	}

	return fmt.Errorf("permission denied")
}

func (am *AuthManager) checkRole(roleName string, key, action, value string, visited map[string]bool) bool {
	if visited[roleName] {
		return false
	}
	visited[roleName] = true

	am.mu.RLock()
	role, ok := am.roles[roleName]
	am.mu.RUnlock()

	if !ok {
		return false
	}

	if checkPerms(role.Permissions, key, action, value) {
		return true
	}

	// Check parent roles
	for _, parent := range role.ParentRoles {
		if am.checkRole(parent, key, action, value, visited) {
			return true
		}
	}

	return false
}

func matchKey(pattern, key string) bool {
	if pattern == "*" {
		return true
	}
	if pattern == key {
		return true
	}
	// Prefix match: "user.asin" matches "user.asin.china"
	if strings.HasSuffix(pattern, "*") {
		prefix := strings.TrimSuffix(pattern, "*")
		return strings.HasPrefix(key, prefix)
	}
	// Hierarchy match: pattern "a.b" matches "a.b.c"
	if strings.HasPrefix(key, pattern+".") {
		return true
	}
	return false
}

func hasAction(actions []string, target string) bool {
	for _, a := range actions {
		if a == target || a == "*" {
			return true
		}
	}
	return false
}

func checkPerms(perms []Permission, key, action, value string) bool {
	for _, perm := range perms {
		if matchKey(perm.KeyPattern, key) && hasAction(perm.Actions, action) {
			// Check Value Constraints if action is write
			if action == ActionWrite && perm.Constraint != nil && value != "" {
				// Try to parse value as float
				val, err := strconv.ParseFloat(value, 64)
				if err == nil {
					if perm.Constraint.Min != nil && val < *perm.Constraint.Min {
						return false
					}
					if perm.Constraint.Max != nil && val > *perm.Constraint.Max {
						return false
					}
				} else {
					// If value is not a number but constraint exists, what to do?
					// Strict mode: fail.
					return false
				}
			}
			return true
		}
	}
	return false
}

// Login authenticates a user and returns a token
// Modified: Sessions are stored in RAFT (Cluster Shared)
func (am *AuthManager) Login(username, password, totpCode, ip string) (string, error) {
	am.mu.RLock()
	if !am.enabled {
		am.mu.RUnlock()
		return "", fmt.Errorf("auth not enabled")
	}
	user, ok := am.users[username]
	am.mu.RUnlock()

	if !ok {
		return "", fmt.Errorf("invalid credentials")
	}

	// Check IP
	if len(user.WhitelistIPs) > 0 {
		allowed := false
		for _, w := range user.WhitelistIPs {
			if w == ip {
				allowed = true
				break
			}
		}
		if !allowed {
			return "", fmt.Errorf("ip not allowed")
		}
	}
	if len(user.BlacklistIPs) > 0 {
		for _, b := range user.BlacklistIPs {
			if b == ip {
				return "", fmt.Errorf("ip blacklisted")
			}
		}
	}

	// Verify Password
	if hashPassword(password, user.Salt) != user.PasswordHash {
		return "", fmt.Errorf("invalid credentials")
	}

	// Verify TOTP
	if user.MFAEnabled {
		if totpCode == "" {
			return "", fmt.Errorf("mfa code required")
		}
		if !validateTOTP(user.MFASecret, totpCode) {
			return "", fmt.Errorf("invalid mfa code")
		}
	}

	// Create Session
	token := generateToken()
	session := &Session{
		Token:    token,
		Username: username,
		LoginIP:  ip,
		Expiry:   time.Now().Add(24 * time.Hour).Unix(),
	}

	// Store session via Raft (Propose)
	data, _ := json.Marshal(session)
	if err := am.server.Set(AuthSessionPrefix+token, string(data)); err != nil {
		return "", err
	}

	return token, nil
}

// Logout invalidates a session (Cluster Shared)
func (am *AuthManager) Logout(token string) error {
	return am.server.Del(AuthSessionPrefix + token)
}

// GetSession returns session info
func (am *AuthManager) GetSession(token string) (*Session, error) {
	am.mu.RLock()
	defer am.mu.RUnlock()
	
	if token == "SYSTEM_INTERNAL" {
		return &Session{Username: "system"}, nil
	}

	session, ok := am.sessions[token]
	if !ok {
		return nil, fmt.Errorf("invalid session")
	}
	if time.Now().Unix() > session.Expiry {
		return nil, fmt.Errorf("session expired")
	}
	return session, nil
}

// GetUser returns user info (Public for internal use)
func (am *AuthManager) GetUser(username string) (*User, error) {
	am.mu.RLock()
	defer am.mu.RUnlock()
	user, ok := am.users[username]
	if !ok {
		return nil, fmt.Errorf("user not found")
	}
	return user, nil
}

// RegisterUser creates a new user
func (am *AuthManager) RegisterUser(username, password string, roles []string) error {
	am.mu.RLock()
	if _, exists := am.users[username]; exists {
		am.mu.RUnlock()
		return fmt.Errorf("user already exists")
	}
	am.mu.RUnlock()

	salt := fmt.Sprintf("%d", time.Now().UnixNano()) // Simple salt
	user := User{
		Username:     username,
		Salt:         salt,
		PasswordHash: hashPassword(password, salt),
		Roles:        roles,
	}
	data, _ := json.Marshal(user)
	// Use SetSync to ensure user is created before returning
	return am.server.SetSync(AuthUserPrefix+username, string(data))
}

// ChangePassword updates a user's password
func (am *AuthManager) ChangePassword(username, newPassword string) error {
	am.mu.RLock()
	user, exists := am.users[username]
	am.mu.RUnlock()
	
	if !exists {
		return fmt.Errorf("user not found")
	}
	
	newUser := *user // Shallow copy to modify
	newUser.Salt = fmt.Sprintf("%d", time.Now().UnixNano())
	newUser.PasswordHash = hashPassword(newPassword, newUser.Salt)
	
	data, _ := json.Marshal(newUser)
	// Use SetSync to ensure password is updated before returning
	return am.server.SetSync(AuthUserPrefix+username, string(data))
}

// UpdateUser updates generic fields of a user (including roles)
func (am *AuthManager) UpdateUser(user User) error {
	am.mu.RLock()
	_, exists := am.users[user.Username]
	am.mu.RUnlock()
	
	if !exists {
		return fmt.Errorf("user not found")
	}
	
	// Ensure we don't overwrite passwordhash/salt blindly if they are empty
	// But usually UpdateUser is called with a fully populated user object from GetUser + mods
	// So we just save it.
	
	data, _ := json.Marshal(user)
	return am.server.SetSync(AuthUserPrefix+user.Username, string(data))
}

// CreateRole creates a new role
func (am *AuthManager) CreateRole(name string) error {
	am.mu.RLock()
	if _, exists := am.roles[name]; exists {
		am.mu.RUnlock()
		return fmt.Errorf("role already exists")
	}
	am.mu.RUnlock()

	role := Role{Name: name}
	data, _ := json.Marshal(role)
	return am.server.SetSync(AuthRolePrefix+name, string(data))
}

// DeleteRole deletes a role
func (am *AuthManager) DeleteRole(name string) error {
	am.mu.RLock()
	if _, exists := am.roles[name]; !exists {
		am.mu.RUnlock()
		return fmt.Errorf("role not found")
	}
	am.mu.RUnlock()
	return am.server.DelSync(AuthRolePrefix + name)
}

// UpdateRole updates an existing role
func (am *AuthManager) UpdateRole(role Role) error {
	am.mu.RLock()
	if _, exists := am.roles[role.Name]; !exists {
		am.mu.RUnlock()
		return fmt.Errorf("role not found")
	}
	am.mu.RUnlock()
	
	data, _ := json.Marshal(role)
	return am.server.SetSync(AuthRolePrefix+role.Name, string(data))
}

// GetRole returns a role definition
func (am *AuthManager) GetRole(name string) (*Role, error) {
	am.mu.RLock()
	defer am.mu.RUnlock()
	role, ok := am.roles[name]
	if !ok {
		return nil, fmt.Errorf("role not found")
	}
	return role, nil
}

// ListUsers lists all users
func (am *AuthManager) ListUsers() []*User {
	am.mu.RLock()
	defer am.mu.RUnlock()
	users := make([]*User, 0, len(am.users))
	for _, u := range am.users {
		// Calculate IsOnline based on active sessions
		isOnline := false
		for _, s := range am.sessions {
			if s.Username == u.Username {
				if time.Now().Unix() <= s.Expiry {
					isOnline = true
					break
				}
			}
		}
		
		// Create a copy to avoid race conditions and not modify cache
		userCopy := *u
		userCopy.IsOnline = isOnline
		users = append(users, &userCopy)
	}
	return users
}

// ListRoles lists all roles
func (am *AuthManager) ListRoles() []*Role {
	am.mu.RLock()
	defer am.mu.RUnlock()
	roles := make([]*Role, 0, len(am.roles))
	for _, r := range am.roles {
		roles = append(roles, r)
	}
	return roles
}

// HasFullAccess checks if the user has "*" permission on all keys (superuser)
// allowing optimizations like pushing limits to DB engine.
func (am *AuthManager) HasFullAccess(token string) bool {
	am.mu.RLock()
	defer am.mu.RUnlock()

	if !am.enabled {
		return true
	}
	if token == "SYSTEM_INTERNAL" {
		return true
	}

	session, ok := am.sessions[token]
	if !ok || time.Now().Unix() > session.Expiry {
		return false
	}

	user, ok := am.users[session.Username]
	if !ok {
		return false
	}

	// Must not have any deny permissions that could block "*"
	// Actually if Deny is empty, and Allow has "*", it's full access.
	// If Deny has something, we can't assume full access blindly.
	if len(user.DenyPermissions) > 0 {
		return false
	}

	// Check Allow
	for _, perm := range user.AllowPermissions {
		if perm.KeyPattern == "*" && hasAction(perm.Actions, "*") {
			return true
		}
	}
	
	// Check Roles
	// This is recursive, so we might skip deeply checking roles for optimization simplicity
	// and only support direct "*" assignment for this optimization.
	// Or we can quickly check if any role has "*"
	for _, roleName := range user.Roles {
		if am.checkRoleFullAccess(roleName, make(map[string]bool)) {
			return true
		}
	}

	return false
}

func (am *AuthManager) checkRoleFullAccess(roleName string, visited map[string]bool) bool {
	if visited[roleName] {
		return false
	}
	visited[roleName] = true

	role, ok := am.roles[roleName]
	if !ok {
		return false
	}

	for _, perm := range role.Permissions {
		if perm.KeyPattern == "*" && hasAction(perm.Actions, "*") {
			return true
		}
	}

	for _, parent := range role.ParentRoles {
		if am.checkRoleFullAccess(parent, visited) {
			return true
		}
	}
	return false
}

// Admin helpers to bootstrap
func (am *AuthManager) CreateRootUser(password string) error {
	salt := "somesalt" // In production use random
	user := User{
		Username:     "root",
		Salt:         salt,
		PasswordHash: hashPassword(password, salt),
		AllowPermissions: []Permission{
			{KeyPattern: "*", Actions: []string{"*"}},
		},
	}
	data, _ := json.Marshal(user)
	return am.server.Set(AuthUserPrefix+"root", string(data))
}

func (am *AuthManager) EnableAuth() error {
	config := AuthConfig{Enabled: true}
	data, _ := json.Marshal(config)
	return am.server.Set(AuthConfigKey, string(data))
}

func (am *AuthManager) DisableAuth() error {
	// Only admin can do this via normal SetAuthenticated
	config := AuthConfig{Enabled: false}
	data, _ := json.Marshal(config)
	return am.server.Set(AuthConfigKey, string(data))
}

// LoadFromDB loads auth data from existing DB
func (am *AuthManager) LoadFromDB() error {
	prefix := SystemKeyPrefix
	
	// We use the DB engine's index to find all system keys
	am.server.DB.Index.WalkPrefix(prefix, func(key string, entry db.IndexEntry) bool {
		// Read value from storage
		// We need to access Storage via Engine.
		// Engine.Storage is exported.
		val, err := am.server.DB.Storage.ReadValue(entry.ValueOffset)
		if err != nil {
			// Skip corrupted or missing values
			return true
		}
		
		// Update Cache
		// Note: We are essentially replaying the state into the cache
		am.UpdateCache(key, val, false)
		return true
	})
	
	return nil
}
