package auth

import (
	"errors"
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"

	"security-center-backend/internal/model"
	"security-center-backend/internal/pkg/logger"
	"security-center-backend/internal/pkg/response"
	"security-center-backend/internal/service"
	"security-center-backend/internal/service/audit"
	"security-center-backend/internal/service/jwt"
)

type Handler struct {
	authService service.AuthService
	jwtManager  *jwt.Manager
	audit       *audit.Service
	logger      *logger.Logger
}

var devDefaultPermissions = []string{
	"auth:manage_users",
	"system:manage_settings",
	"caasm:view_assets",
	"caasm:manage_assets",
	"caasm:manage_api_assets",
	"caasm:export_ports",
	"scan:view_batches",
	"scan:create_batches",
	"scan:manage_batches",
	"scan:view_evidence",
	"vuln:view_findings",
	"vuln:manage_findings",
	"vuln:view_sensitive_details",
	"vuln:edit_sensitive_details",
	"vuln:edit_details",
	"vuln:authorize_viewers",
	"vuln:assign_owner",
	"vuln:request_retest",
	"vuln:close_findings",
	"audit:view_logs",
}

func NewHandler(authService service.AuthService, jwtManager *jwt.Manager, auditLogger *audit.Service, log *logger.Logger) *Handler {
	return &Handler{authService: authService, jwtManager: jwtManager, audit: auditLogger, logger: log}
}

func (h *Handler) RegisterRoutes(rg *gin.RouterGroup, authMiddleware gin.HandlerFunc) {
	rg.POST("/auth/google/login", h.GoogleLogin)
	rg.GET("/auth/google/callback", h.GoogleCallback)
	rg.POST("/auth/auth0/login", h.Auth0Login)
	rg.GET("/auth/auth0/callback", h.Auth0Callback)
	rg.POST("/auth/refresh", h.RefreshToken)

	protected := rg.Group("")
	if authMiddleware != nil {
		protected.Use(authMiddleware)
	}
	protected.POST("/auth/logout", h.Logout)
	protected.GET("/auth/me", h.Me)
}

func (h *Handler) GoogleLogin(c *gin.Context) {
	h.startLogin(c, "google")
}

func (h *Handler) Auth0Login(c *gin.Context) {
	h.startLogin(c, "auth0")
}

func (h *Handler) startLogin(c *gin.Context, provider string) {
	if h.logger != nil {
		h.logger.Info("auth login requested", "provider", provider, "path", c.Request.URL.Path)
	}
	url, err := h.authService.GenerateLoginURL(c.Request.Context(), provider)
	if err != nil {
		if errors.Is(err, service.ErrSSODisabled) {
			if h.logger != nil {
				h.logger.Info("auth login using dev bypass", "provider", provider)
			}
			session, sessionErr := h.authService.GenerateDevSession(c.Request.Context())
			if sessionErr != nil {
				devSession := h.generateQuickDevSession()
				if devSession != nil {
					if h.logger != nil {
						h.logger.Warn("auth login fell back to quick dev session", "provider", provider)
					}
					response.Success(c, gin.H{"session": devSession})
					return
				}

				if h.logger != nil {
					h.logger.Error("auth login failed to generate dev session", "provider", provider, "error", sessionErr)
				}
				c.JSON(http.StatusInternalServerError, gin.H{
					"code":      http.StatusInternalServerError,
					"message":   "failed to generate dev session",
					"error":     sessionErr.Error(),
					"timestamp": time.Now().Unix(),
				})
				return
			}
			response.Success(c, gin.H{"session": session})
			action := "auth.login"
			if provider != "" {
				action = "auth.login." + provider
			}
			h.logAuthEvent(c, action, session.User.ID, map[string]interface{}{"email": session.User.Email, "provider": provider})
			return
		}
		if h.logger != nil {
			h.logger.Warn("auth login failed before redirect", "provider", provider, "error", err)
		}
		response.Error(c, http.StatusBadRequest, err.Error())
		return
	}

	if h.logger != nil {
		h.logger.Info("auth login redirect generated", "provider", provider)
	}
	response.Success(c, gin.H{"url": url})
}

// generateQuickDevSession creates a dev session without database access
// This is useful for development/testing when database is not available
func (h *Handler) generateQuickDevSession() map[string]interface{} {
	if h.jwtManager == nil {
		return nil
	}

	// Generate tokens using JWT manager directly
	accessToken, err := h.jwtManager.GenerateAccessToken(0, "dev.user@example.com", []string{"admin"}, devDefaultPermissions)
	if err != nil {
		return nil
	}

	refreshToken, err := h.jwtManager.GenerateRefreshToken(0)
	if err != nil {
		return nil
	}

	return map[string]interface{}{
		"user": map[string]interface{}{
			"id":                   "dev-user",
			"email":                "dev.user@example.com",
			"name":                 "Development User",
			"roles":                []string{"admin"},
			"permissions":          devDefaultPermissions,
			"status":               model.UserStatusActive,
			"employee_link_status": model.EmployeeLinkStatusPending,
		},
		"access_token":  accessToken,
		"refresh_token": refreshToken,
		"expires_in":    int64(h.jwtManager.AccessTTL().Seconds()),
	}
}

func (h *Handler) GoogleCallback(c *gin.Context) {
	h.handleOAuthCallback(c, "google")
}

func (h *Handler) Auth0Callback(c *gin.Context) {
	h.handleOAuthCallback(c, "auth0")
}

func (h *Handler) handleOAuthCallback(c *gin.Context, provider string) {
	code := c.Query("code")
	state := c.Query("state")
	if h.logger != nil {
		h.logger.Info("auth callback received", "provider", provider, "has_code", code != "", "has_state", state != "")
	}
	session, err := h.authService.HandleCallback(c.Request.Context(), provider, code, state)
	if err != nil {
		if errors.Is(err, service.ErrSSODisabled) {
			if h.logger != nil {
				h.logger.Info("auth callback using dev bypass", "provider", provider)
			}
			session, sessionErr := h.authService.GenerateDevSession(c.Request.Context())
			if sessionErr != nil {
				devSession := h.generateQuickDevSession()
				if devSession != nil {
					if h.logger != nil {
						h.logger.Warn("auth callback fell back to quick dev session", "provider", provider)
					}
					response.Success(c, devSession)
					return
				}
				if h.logger != nil {
					h.logger.Error("auth callback failed to generate dev session", "provider", provider, "error", sessionErr)
				}
				response.Error(c, http.StatusInternalServerError, sessionErr.Error())
				return
			}
			response.Success(c, session)
			action := "auth.login"
			if provider != "" {
				action = "auth.login." + provider
			}
			h.logAuthEvent(c, action, session.User.ID, map[string]interface{}{"email": session.User.Email, "provider": provider})
			return
		}
		if h.logger != nil {
			h.logger.Warn("auth callback failed", "provider", provider, "error", err)
		}
		response.Error(c, http.StatusUnauthorized, err.Error())
		return
	}

	if h.logger != nil {
		h.logger.Info(
			"auth callback completed",
			"provider", provider,
			"user_id", session.User.ID,
			"employee_id", session.User.EmployeeID,
			"employee_link_status", session.User.EmployeeLinkStatus,
		)
	}
	response.Success(c, session)
	action := "auth.login"
	if provider != "" {
		action = "auth.login." + provider
	}
	h.logAuthEvent(c, action, session.User.ID, map[string]interface{}{"email": session.User.Email, "provider": provider})
}

func (h *Handler) RefreshToken(c *gin.Context) {
	var req service.RefreshTokenRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.Error(c, http.StatusBadRequest, "invalid request")
		return
	}

	token, err := h.authService.RefreshAccessToken(c.Request.Context(), req.RefreshToken)
	if err != nil {
		response.Error(c, http.StatusUnauthorized, err.Error())
		return
	}

	response.Success(c, gin.H{
		"access_token":  token.AccessToken,
		"refresh_token": token.RefreshToken,
		"expires_in":    token.ExpiresIn,
	})
	if claims, err := h.jwtManager.Validate(token.AccessToken); err == nil {
		h.logAuthEvent(c, "auth.refresh", strconv.FormatUint(claims.UserID, 10), map[string]interface{}{"email": claims.Email})
	}
}

func (h *Handler) Logout(c *gin.Context) {
	token := c.GetString("access_token")
	if token == "" {
		response.Error(c, http.StatusBadRequest, "missing token")
		return
	}

	if err := h.authService.Logout(c.Request.Context(), token); err != nil {
		response.Error(c, http.StatusInternalServerError, err.Error())
		return
	}
	if claimsValue, ok := c.Get("current_user"); ok {
		if claims, ok := claimsValue.(*jwt.Claims); ok {
			h.logAuthEvent(c, "auth.logout", strconv.FormatUint(claims.UserID, 10), map[string]interface{}{"email": claims.Email})
		}
	}
	response.Success(c, gin.H{"status": "logged_out"})
}

func (h *Handler) Me(c *gin.Context) {
	currentUser, exists := c.Get("current_user")
	if !exists {
		if !h.authService.IsSSOEnabled() {
			session, err := h.authService.GenerateDevSession(c.Request.Context())
			if err != nil {
				// Fallback: use quick dev session if database is unavailable
				devSession := h.generateQuickDevSession()
				if devSession != nil {
					response.Success(c, devSession)
					return
				}
				response.Error(c, http.StatusInternalServerError, err.Error())
				return
			}
			response.Success(c, session.User)
			return
		}
		response.Error(c, http.StatusUnauthorized, "not authenticated")
		return
	}

	claims, ok := currentUser.(*jwt.Claims)
	if !ok {
		response.Error(c, http.StatusUnauthorized, "invalid user context")
		return
	}

	profile, err := h.authService.GetCurrentUserProfile(c.Request.Context(), claims.UserID)
	if err != nil {
		response.Error(c, http.StatusUnauthorized, "failed to load current user profile")
		return
	}

	response.Success(c, profile)
}

func (h *Handler) logAuthEvent(c *gin.Context, action string, userID string, metadata map[string]interface{}) {
	if h.audit == nil {
		return
	}
	entry := audit.Entry{
		Action:       action,
		ResourceType: "auth",
		ResourceID:   userID,
		Metadata:     model.JSONB(metadata),
		IPAddress:    c.ClientIP(),
		UserAgent:    c.GetHeader("User-Agent"),
	}
	if id, err := strconv.ParseUint(userID, 10, 64); err == nil {
		entry.UserID = &id
	}
	h.audit.Log(c.Request.Context(), entry)
}
