package handlers

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"

	"github.com/trackeep/backend/config"
	"github.com/trackeep/backend/models"
)

type centralizedOAuthUser struct {
	ID        int    `json:"id"`
	GitHubID  int    `json:"github_id"`
	Username  string `json:"username"`
	Email     string `json:"email"`
	Name      string `json:"name"`
	AvatarURL string `json:"avatar_url"`
}

func getOAuthServiceURL() string {
	return config.ControlServiceURL
}

func headerValue(headers http.Header, key string) string {
	raw := strings.TrimSpace(headers.Get(key))
	if raw == "" {
		return ""
	}

	for _, part := range strings.Split(raw, ",") {
		candidate := strings.TrimSpace(part)
		if candidate != "" {
			return candidate
		}
	}

	return ""
}

func backendPublicBaseURL(r *http.Request) string {
	if baseURL := strings.TrimSpace(os.Getenv("PUBLIC_API_URL")); baseURL != "" {
		return strings.TrimRight(baseURL, "/")
	}
	if baseURL := strings.TrimSpace(os.Getenv("PUBLIC_BASE_URL")); baseURL != "" {
		return strings.TrimRight(baseURL, "/")
	}

	scheme := "http"
	if forwardedProto := headerValue(r.Header, "X-Forwarded-Proto"); forwardedProto != "" {
		scheme = forwardedProto
	} else if r.TLS != nil {
		scheme = "https"
	}

	host := headerValue(r.Header, "X-Forwarded-Host")
	if host == "" {
		host = strings.TrimSpace(r.Host)
	}
	if host == "" {
		return ""
	}

	return fmt.Sprintf("%s://%s", scheme, host)
}

func normalizeFrontendRedirectURL(raw string) string {
	value := strings.TrimSpace(raw)
	if value == "" {
		return ""
	}

	parsed, err := url.Parse(value)
	if err != nil || parsed.Scheme == "" || parsed.Host == "" {
		return ""
	}

	if parsed.Path == "" || parsed.Path == "/" {
		parsed.Path = "/auth/callback"
	}

	return parsed.String()
}

func resolveFrontendRedirectURL(r *http.Request) string {
	if value := normalizeFrontendRedirectURL(r.URL.Query().Get("frontend_redirect")); value != "" {
		return value
	}

	if value := normalizeFrontendRedirectURL(os.Getenv("FRONTEND_URL")); value != "" {
		return value
	}

	if origin := normalizeFrontendRedirectURL(r.Header.Get("Origin")); origin != "" {
		return origin
	}

	referer := strings.TrimSpace(r.Header.Get("Referer"))
	if referer != "" {
		if parsed, err := url.Parse(referer); err == nil && parsed.Scheme != "" && parsed.Host != "" {
			return normalizeFrontendRedirectURL((&url.URL{
				Scheme: parsed.Scheme,
				Host:   parsed.Host,
				Path:   "/auth/callback",
			}).String())
		}
	}

	return ""
}

func buildGitHubUserCallbackURL(r *http.Request) string {
	baseURL := backendPublicBaseURL(r)
	if baseURL == "" {
		return ""
	}

	callbackURL, err := url.Parse(baseURL + "/api/v1/auth/github/callback")
	if err != nil {
		return ""
	}

	return callbackURL.String()
}

func buildFrontendCallbackRedirectURL(frontendRedirect, token string) string {
	redirectTarget := normalizeFrontendRedirectURL(frontendRedirect)
	if redirectTarget == "" {
		redirectTarget = normalizeFrontendRedirectURL(os.Getenv("FRONTEND_URL"))
	}
	if redirectTarget == "" {
		return ""
	}

	parsed, err := url.Parse(redirectTarget)
	if err != nil {
		return ""
	}

	query := parsed.Query()
	query.Set("token", token)
	parsed.RawQuery = query.Encode()

	return parsed.String()
}
func firstNonEmpty(values ...string) string {
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed != "" {
			return trimmed
		}
	}
	return ""
}

func uniqueUsername(base string, db *gorm.DB, excludeUserID uint) string {
	candidate := strings.TrimSpace(base)
	if candidate == "" {
		candidate = "user"
	}

	for suffix := 0; ; suffix++ {
		username := candidate
		if suffix > 0 {
			username = fmt.Sprintf("%s-%d", candidate, suffix+1)
		}

		var existing models.User
		err := db.Where("username = ?", username).First(&existing).Error
		if err == nil {
			if excludeUserID != 0 && existing.ID == excludeUserID {
				return username
			}
			continue
		}
		if err == gorm.ErrRecordNotFound {
			return username
		}
		return username
	}
}

func upsertCentralizedOAuthUser(db *gorm.DB, controllerUser centralizedOAuthUser) (*models.User, error) {
	var user models.User
	var err error

	normalizedEmail := strings.TrimSpace(controllerUser.Email)
	normalizedUsername := firstNonEmpty(controllerUser.Username, strings.Split(normalizedEmail, "@")[0], "user")
	fullName := firstNonEmpty(controllerUser.Name, controllerUser.Username, normalizedEmail)
	provider := "email"
	if controllerUser.GitHubID != 0 {
		provider = "github"
		err = db.Where("github_id = ?", controllerUser.GitHubID).First(&user).Error
	} else {
		err = gorm.ErrRecordNotFound
	}

	if err != nil && normalizedEmail != "" {
		err = db.Where("email = ?", normalizedEmail).First(&user).Error
	}

	if err == nil {
		updates := map[string]interface{}{
			"email":      normalizedEmail,
			"username":   uniqueUsername(normalizedUsername, db, user.ID),
			"full_name":  fullName,
			"avatar_url": controllerUser.AvatarURL,
			"provider":   provider,
		}
		if controllerUser.GitHubID != 0 {
			updates["github_id"] = controllerUser.GitHubID
		}

		now := time.Now()
		updates["last_login_at"] = &now

		if err := db.Model(&user).Updates(updates).Error; err != nil {
			return nil, err
		}
		if err := db.First(&user, user.ID).Error; err != nil {
			return nil, err
		}

		return &user, nil
	}

	if err != gorm.ErrRecordNotFound {
		return nil, err
	}

	var userCount int64
	if err := db.Model(&models.User{}).Count(&userCount).Error; err != nil {
		return nil, err
	}

	randomPassword := generateRandomString(32)
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(randomPassword), bcrypt.DefaultCost)
	if err != nil {
		return nil, err
	}

	role := "user"
	if userCount == 0 {
		role = "admin"
	}

	now := time.Now()
	user = models.User{
		Email:       normalizedEmail,
		Username:    uniqueUsername(normalizedUsername, db, 0),
		Password:    string(hashedPassword),
		FullName:    fullName,
		Role:        role,
		Theme:       "dark",
		GitHubID:    controllerUser.GitHubID,
		AvatarURL:   controllerUser.AvatarURL,
		Provider:    provider,
		LastLoginAt: &now,
	}

	if err := db.Create(&user).Error; err != nil {
		return nil, err
	}

	_ = ensureMessagingDefaults(db, user.ID)

	return &user, nil
}
