package iam

import (
	"database/sql"
	"fmt"
	"time"

	"github.com/atlasos/calypso/internal/common/database"
)

// User represents a system user
type User struct {
	ID           string
	Username     string
	Email        string
	PasswordHash string
	FullName     string
	IsActive     bool
	IsSystem     bool
	CreatedAt    time.Time
	UpdatedAt    time.Time
	LastLoginAt  sql.NullTime
	Roles        []string
	Permissions  []string
}

// GetUserByID retrieves a user by ID
func GetUserByID(db *database.DB, userID string) (*User, error) {
	query := `
		SELECT id, username, email, password_hash, full_name, is_active, is_system,
		       created_at, updated_at, last_login_at
		FROM users
		WHERE id = $1
	`

	var user User
	var lastLogin sql.NullTime
	err := db.QueryRow(query, userID).Scan(
		&user.ID, &user.Username, &user.Email, &user.PasswordHash,
		&user.FullName, &user.IsActive, &user.IsSystem,
		&user.CreatedAt, &user.UpdatedAt, &lastLogin,
	)
	if err != nil {
		return nil, err
	}

	user.LastLoginAt = lastLogin
	return &user, nil
}

// GetUserByUsername retrieves a user by username
func GetUserByUsername(db *database.DB, username string) (*User, error) {
	query := `
		SELECT id, username, email, password_hash, full_name, is_active, is_system,
		       created_at, updated_at, last_login_at
		FROM users
		WHERE username = $1
	`

	var user User
	var lastLogin sql.NullTime
	err := db.QueryRow(query, username).Scan(
		&user.ID, &user.Username, &user.Email, &user.PasswordHash,
		&user.FullName, &user.IsActive, &user.IsSystem,
		&user.CreatedAt, &user.UpdatedAt, &lastLogin,
	)
	if err != nil {
		return nil, err
	}

	user.LastLoginAt = lastLogin
	return &user, nil
}

// GetUserRoles retrieves all roles for a user
func GetUserRoles(db *database.DB, userID string) ([]string, error) {
	query := `
		SELECT r.name
		FROM roles r
		INNER JOIN user_roles ur ON r.id = ur.role_id
		WHERE ur.user_id = $1
	`

	rows, err := db.Query(query, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var roles []string
	for rows.Next() {
		var role string
		if err := rows.Scan(&role); err != nil {
			return []string{}, err
		}
		roles = append(roles, role)
	}

	if roles == nil {
		roles = []string{}
	}
	return roles, rows.Err()
}

// GetUserPermissions retrieves all permissions for a user (via roles)
func GetUserPermissions(db *database.DB, userID string) ([]string, error) {
	query := `
		SELECT DISTINCT p.name
		FROM permissions p
		INNER JOIN role_permissions rp ON p.id = rp.permission_id
		INNER JOIN user_roles ur ON rp.role_id = ur.role_id
		WHERE ur.user_id = $1
	`

	rows, err := db.Query(query, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var permissions []string
	for rows.Next() {
		var perm string
		if err := rows.Scan(&perm); err != nil {
			return []string{}, err
		}
		permissions = append(permissions, perm)
	}

	if permissions == nil {
		permissions = []string{}
	}
	return permissions, rows.Err()
}

// AddUserRole assigns a role to a user
func AddUserRole(db *database.DB, userID, roleID, assignedBy string) error {
	query := `
		INSERT INTO user_roles (user_id, role_id, assigned_by)
		VALUES ($1, $2, $3)
		ON CONFLICT (user_id, role_id) DO NOTHING
	`
	result, err := db.Exec(query, userID, roleID, assignedBy)
	if err != nil {
		return fmt.Errorf("failed to insert user role: %w", err)
	}

	// Check if row was actually inserted (not just skipped due to conflict)
	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		// Row already exists, this is not an error but we should know about it
		return nil // ON CONFLICT DO NOTHING means this is expected
	}

	return nil
}

// RemoveUserRole removes a role from a user
func RemoveUserRole(db *database.DB, userID, roleID string) error {
	query := `DELETE FROM user_roles WHERE user_id = $1 AND role_id = $2`
	_, err := db.Exec(query, userID, roleID)
	return err
}

// GetRoleIDByName retrieves a role ID by name
func GetRoleIDByName(db *database.DB, roleName string) (string, error) {
	var roleID string
	err := db.QueryRow("SELECT id FROM roles WHERE name = $1", roleName).Scan(&roleID)
	return roleID, err
}
