package user

import (
	"encoding/json"
	"errors"
	"log"
	"log/slog"
	"net/http"
	"regexp"
	"strings"

	"crussell/db"
	"crussell/handlers/auth"
	"crussell/internal/validators"
	"github.com/jackc/pgx/v5"
)

type CreateGuestUserRequest struct {
	FirstName string `json:"firstName" validate:"required,min=1,max=50"`
	LastName  string `json:"lastName"  validate:"required,min=1,max=50"`
	Email     string `json:"email"     validate:"required,email,max=254"`
	Phone     string `json:"phone"     validate:"required"`
}

type CreateGuestUserResponse struct {
	ID   string `json:"id"`
	Role string `json:"role"`
}

// POST /api/user/guest
func CreateGuestUserHandler(w http.ResponseWriter, r *http.Request) {
	var req CreateGuestUserRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request", http.StatusBadRequest)
		return
	}

	if err := validators.Validate.Struct(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Normalize input
	req.FirstName = strings.TrimSpace(req.FirstName)
	req.LastName = strings.TrimSpace(req.LastName)
	req.Email = strings.ToLower(strings.TrimSpace(req.Email))
	req.Phone = strings.TrimSpace(req.Phone)

	// Validate all 4 fields are non-empty
	if req.FirstName == "" || req.LastName == "" || req.Email == "" || req.Phone == "" {
		http.Error(w, "first name, last name, email and phone are required", http.StatusBadRequest)
		return
	}

	// Validate names (unicode letters, spaces, hyphen, apostrophe, dot)
	nameRegex := regexp.MustCompile(`^[\p{L}\p{M}\s\-'\.]+$`)
	if !nameRegex.MatchString(req.FirstName) || !nameRegex.MatchString(req.LastName) {
		http.Error(w, "invalid characters in name", http.StatusBadRequest)
		return
	}

	// Validate name lengths
	if len(req.FirstName) < 1 || len(req.FirstName) > 50 {
		http.Error(w, "first name must be 1-50 characters", http.StatusBadRequest)
		return
	}
	if len(req.LastName) < 1 || len(req.LastName) > 50 {
		http.Error(w, "last name must be 1-50 characters", http.StatusBadRequest)
		return
	}

	// Validate email format
	if err := validators.ValidateEmail(req.Email); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// Normalize phone (strip non-digit/+ chars)
	req.Phone = strings.Map(func(r rune) rune {
		if (r >= '0' && r <= '9') || r == '+' {
			return r
		}
		return -1
	}, req.Phone)

	// Validate UK phone number
	phone, err := auth.ValidateUKPhoneNumber(req.Phone)
	if err != nil {
		http.Error(w, "invalid phone number format", http.StatusBadRequest)
		return
	}
	req.Phone = strings.TrimSpace(phone)

	// Check if email already exists with a registered (non-guest) role
	var existingRole string
	err = db.Conn.QueryRow(r.Context(), `
		SELECT account_role FROM users WHERE email = $1
	`, req.Email).Scan(&existingRole)

	if err == nil && existingRole != "guest" {
		http.Error(w, "Email already registered - please log in to book", http.StatusConflict)
		return
	}

	// Always create a new guest user — even if email/phone is used by another guest.
	// Each booking is a disposable account; we don't track identity across guest bookings.

	// Create new guest user
	tx, err := db.Conn.Begin(r.Context())
	if err != nil {
		log.Printf("Failed to begin transaction: %v", err)
		http.Error(w, "Internal server error", http.StatusInternalServerError)
		return
	}
	defer func() {
    if err := tx.Rollback(r.Context()); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	var userID string
	err = tx.QueryRow(r.Context(), `
		INSERT INTO users
			(n_first_name, n_last_name, email, phone, date_of_birth,
			 account_role, account_type, password_hash, privacy_policy_and_terms_consent)
		VALUES ($1, $2, $3, $4, '1900-01-01', 'guest', 'email', NULL, TRUE)
		RETURNING id
	`, req.FirstName, req.LastName, req.Email, req.Phone).Scan(&userID)

	if err != nil {
		log.Printf("Failed to create guest user: %v", err)
		http.Error(w, "failed to create guest user", http.StatusInternalServerError)
		return
	}

	if err := tx.Commit(r.Context()); err != nil {
		log.Printf("Failed to commit transaction: %v", err)
		http.Error(w, "Internal server error", http.StatusInternalServerError)
		return
	}

	// Note: We intentionally don't sync to CardDAV - guests don't need calendar contacts

	w.WriteHeader(http.StatusCreated)
	_ = json.NewEncoder(w).Encode(CreateGuestUserResponse{ID: userID, Role: "guest"})
}

// GET /api/check-email?email=...&firstName=...&lastName=...&phone=...
// Returns { suggestion: "login" | "check" | null } based on whether the email
// belongs to a registered user and how closely the provided details match.
// Relies on nginx restricting access to frontend-only traffic.
func CheckEmailHandler(w http.ResponseWriter, r *http.Request) {
	email := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("email")))
	if email == "" {
		http.Error(w, "email query parameter required", http.StatusBadRequest)
		return
	}

	if err := validators.ValidateEmail(email); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	firstName := strings.TrimSpace(r.URL.Query().Get("firstName"))
	lastName := strings.TrimSpace(r.URL.Query().Get("lastName"))
	phone := strings.TrimSpace(r.URL.Query().Get("phone"))

	var dbFirstName, dbLastName, dbPhone *string
	err := db.Conn.QueryRow(r.Context(), `
		SELECT n_first_name, n_last_name, phone
		FROM users
		WHERE email = $1 AND account_role != 'guest'
	`, email).Scan(&dbFirstName, &dbLastName, &dbPhone)

	var suggestion *string
	if err == nil {
		matchesNames := firstName != "" && lastName != "" &&
			dbFirstName != nil && dbLastName != nil &&
			strings.EqualFold(firstName, *dbFirstName) &&
			strings.EqualFold(lastName, *dbLastName)

		matchesPhone := phone != "" &&
			dbPhone != nil &&
			phone == *dbPhone

		if matchesNames && matchesPhone {
			s := "login"
			suggestion = &s
		} else {
			s := "check"
			suggestion = &s
		}
	} else if !errors.Is(err, pgx.ErrNoRows) {
		log.Printf("Failed to check email: %v", err)
		http.Error(w, "database error", http.StatusInternalServerError)
		return
	}

	_ = json.NewEncoder(w).Encode(map[string]any{
		"suggestion": suggestion,
	})
}
