package users

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"

	"github.com/ultisuite/ulti-backend/internal/auth"
)

// ProvisionEmail returns the email stored for a newly provisioned user.
func ProvisionEmail(claims *auth.Claims) string {
	if claims == nil {
		return ""
	}
	email := strings.TrimSpace(claims.Email)
	if email != "" {
		return email
	}
	return claims.Sub + "@unknown.ultimail.local"
}

// EnsureUser inserts or updates the Ultimail user row for OIDC claims and returns the internal UUID.
func EnsureUser(ctx context.Context, db *pgxpool.Pool, claims *auth.Claims) (string, error) {
	if db == nil {
		return "", fmt.Errorf("database not configured")
	}
	if claims == nil || strings.TrimSpace(claims.Sub) == "" {
		return "", fmt.Errorf("missing subject claim")
	}

	email := ProvisionEmail(claims)
	name := strings.TrimSpace(claims.Name)

	var userCount int64
	if err := db.QueryRow(ctx, `SELECT COUNT(*) FROM users`).Scan(&userCount); err != nil {
		return "", fmt.Errorf("count users: %w", err)
	}
	isFirstUser := userCount == 0

	var userID string
	err := db.QueryRow(ctx, `
		INSERT INTO users (external_id, email, name, platform_admin)
		VALUES ($1, $2, $3, $4)
		ON CONFLICT (external_id) DO UPDATE SET
			email = EXCLUDED.email,
			name = EXCLUDED.name,
			updated_at = NOW()
		RETURNING id
	`, claims.Sub, email, name, isFirstUser).Scan(&userID)
	if err != nil {
		return "", fmt.Errorf("provision user: %w", err)
	}
	if isFirstUser {
		if err := bootstrapFirstUserAdmin(ctx, db, userID); err != nil {
			return "", fmt.Errorf("bootstrap platform admin: %w", err)
		}
	}
	return userID, nil
}

// LookupUserID returns the internal user UUID for an OIDC subject.
func LookupUserID(ctx context.Context, db *pgxpool.Pool, externalID string) (string, error) {
	if db == nil {
		return "", fmt.Errorf("database not configured")
	}
	externalID = strings.TrimSpace(externalID)
	if externalID == "" {
		return "", pgx.ErrNoRows
	}
	var userID string
	err := db.QueryRow(ctx, `SELECT id::text FROM users WHERE external_id = $1`, externalID).Scan(&userID)
	return userID, err
}

// LookupIdentityByEmail returns the OIDC subject and stored email for a user row.
func LookupIdentityByEmail(ctx context.Context, db *pgxpool.Pool, email string) (externalID, storedEmail string, err error) {
	if db == nil {
		return "", "", fmt.Errorf("database not configured")
	}
	email = strings.ToLower(strings.TrimSpace(email))
	if email == "" {
		return "", "", pgx.ErrNoRows
	}
	err = db.QueryRow(ctx, `
		SELECT external_id, email FROM users WHERE lower(email) = $1
	`, email).Scan(&externalID, &storedEmail)
	return externalID, storedEmail, err
}

// LookupUserIDByEmail returns the internal user UUID for a stored email address.
func LookupUserIDByEmail(ctx context.Context, db *pgxpool.Pool, email string) (string, error) {
	if db == nil {
		return "", fmt.Errorf("database not configured")
	}
	email = strings.ToLower(strings.TrimSpace(email))
	if email == "" {
		return "", pgx.ErrNoRows
	}
	var userID string
	err := db.QueryRow(ctx, `SELECT id::text FROM users WHERE lower(email) = $1`, email).Scan(&userID)
	return userID, err
}

// ResolveProvisionUser finds an existing user by external_id or email, or creates one from Authentik enrollment data.
func ResolveProvisionUser(ctx context.Context, db *pgxpool.Pool, externalID, email, name string) (string, error) {
	externalID = strings.TrimSpace(externalID)
	email = strings.ToLower(strings.TrimSpace(email))

	if externalID != "" {
		userID, err := LookupUserID(ctx, db, externalID)
		if err == nil {
			return userID, nil
		}
		if !errors.Is(err, pgx.ErrNoRows) {
			return "", err
		}
	}
	if email != "" {
		userID, err := LookupUserIDByEmail(ctx, db, email)
		if err == nil {
			return userID, nil
		}
		if !errors.Is(err, pgx.ErrNoRows) {
			return "", err
		}
	}
	if externalID == "" {
		return "", fmt.Errorf("cannot provision user without external_id or existing email")
	}
	return EnsureUser(ctx, db, &auth.Claims{
		Sub:   externalID,
		Email: email,
		Name:  name,
	})
}
