package handlers

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"path/filepath"
	"time"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"

	"github.com/RyanCopley/skybridge/kms/internal/auth"
	"github.com/RyanCopley/skybridge/kms/internal/config"
	"github.com/RyanCopley/skybridge/kms/internal/domain"
	"github.com/RyanCopley/skybridge/kms/internal/errors"
	"github.com/RyanCopley/skybridge/kms/internal/services"
)

// AuthHandler handles authentication-related HTTP requests
type AuthHandler struct {
	authService     services.AuthenticationService
	tokenService    services.TokenService
	headerValidator *auth.HeaderValidator
	config          config.ConfigProvider
	errorHandler    *errors.ErrorHandler
	logger          *zap.Logger
	loginTemplate   *template.Template
}

// LoginPageData represents data passed to the login HTML template
type LoginPageData struct {
	Token           string
	TokenJSON       template.JS
	RedirectURLJSON template.JS
	ExpiresAt       string
	AppID           string
	UserID          string
}

// NewAuthHandler creates a new auth handler
func NewAuthHandler(
	authService services.AuthenticationService,
	tokenService services.TokenService,
	config config.ConfigProvider,
	logger *zap.Logger,
) *AuthHandler {
	// Load login template
	templatePath := filepath.Join("templates", "login.html")
	loginTemplate, err := template.ParseFiles(templatePath)
	if err != nil {
		logger.Error("Failed to load login template", zap.Error(err), zap.String("path", templatePath))
		// Template loading failure is not fatal, we'll fall back to JSON
	}

	return &AuthHandler{
		authService:     authService,
		tokenService:    tokenService,
		headerValidator: auth.NewHeaderValidator(config, logger),
		config:          config,
		errorHandler:    errors.NewErrorHandler(logger),
		logger:          logger,
		loginTemplate:   loginTemplate,
	}
}

// Login handles login requests (both GET for HTML and POST for JSON)
func (h *AuthHandler) Login(c *gin.Context) {
	// Handle GET requests or requests that prefer HTML
	acceptHeader := c.GetHeader("Accept")
	contentType := c.GetHeader("Content-Type")
	
	isJSONRequest := (c.Request.Method == "POST" && (contentType == "application/json" || 
		(acceptHeader != "" && (acceptHeader == "application/json" || 
		 (acceptHeader != "text/html" && acceptHeader != "*/*")))))

	var req domain.LoginRequest
	
	if isJSONRequest {
		// Handle JSON POST request (existing API behavior)
		if err := c.ShouldBindJSON(&req); err != nil {
			h.errorHandler.HandleValidationError(c, "request_body", "Invalid login request format")
			return
		}
	} else {
		// Handle HTML request (GET or POST with form data)
		req.AppID = c.Query("app_id")
		req.RedirectURI = c.Query("redirect_uri")
		
		// Parse permissions from query parameter (comma-separated)
		if perms := c.Query("permissions"); perms != "" {
			// Simple parsing for comma-separated permissions
			req.Permissions = []string{perms} // Simplified for this example
		}
		
		// If no app_id provided, show error
		if req.AppID == "" {
			h.renderLoginError(c, "Missing required parameter: app_id", isJSONRequest)
			return
		}
	}

	// Validate authentication headers with HMAC signature
	userContext, err := h.headerValidator.ValidateAuthenticationHeaders(c.Request)
	if err != nil {
		if isJSONRequest {
			h.errorHandler.HandleAuthenticationError(c, err)
		} else {
			h.renderLoginError(c, "Authentication failed: "+err.Error(), isJSONRequest)
		}
		return
	}

	h.logger.Info("Processing login request", zap.String("user_id", userContext.UserID), zap.String("app_id", req.AppID))

	// Generate user token
	token, err := h.tokenService.GenerateUserToken(c.Request.Context(), req.AppID, userContext.UserID, req.Permissions)
	if err != nil {
		if isJSONRequest {
			h.errorHandler.HandleInternalError(c, err)
		} else {
			h.renderLoginError(c, "Failed to generate token: "+err.Error(), isJSONRequest)
		}
		return
	}

	// For JSON requests without redirect URI, return token directly
	if isJSONRequest && req.RedirectURI == "" {
		c.JSON(http.StatusOK, gin.H{
			"token":      token,
			"user_id":    userContext.UserID,
			"app_id":     req.AppID,
			"expires_in": 604800, // 7 days in seconds
		})
		return
	}

	// Handle redirect flows - always deliver token via query parameter
	var redirectURL string
	if req.RedirectURI != "" {
		// Generate a secure state parameter for CSRF protection
		state := h.generateSecureState(userContext.UserID, req.AppID)
		redirectURL = req.RedirectURI + "?token=" + token + "&state=" + state
	}

	// Return appropriate response format
	if isJSONRequest {
		response := domain.LoginResponse{
			RedirectURL: redirectURL,
		}
		c.JSON(http.StatusOK, response)
	} else {
		// Render HTML page
		h.renderLoginPage(c, token, redirectURL, userContext.UserID, req.AppID)
	}
}

// renderLoginPage renders the HTML login page with token information
func (h *AuthHandler) renderLoginPage(c *gin.Context, token, redirectURL, userID, appID string) {
	if h.loginTemplate == nil {
		// Fallback to JSON if template not available
		c.JSON(http.StatusOK, gin.H{
			"token":       token,
			"redirect_url": redirectURL,
			"user_id":     userID,
			"app_id":      appID,
			"message":     "Login successful - HTML template not available",
		})
		return
	}

	// Prepare template data
	tokenJSON, _ := json.Marshal(token)
	redirectURLJSON, _ := json.Marshal(redirectURL)
	
	data := LoginPageData{
		Token:           token,
		TokenJSON:       template.JS(tokenJSON),
		RedirectURLJSON: template.JS(redirectURLJSON),
		ExpiresAt:       time.Now().Add(7 * 24 * time.Hour).Format("Jan 2, 2006 at 3:04 PM MST"),
		AppID:           appID,
		UserID:          userID,
	}

	c.Header("Content-Type", "text/html; charset=utf-8")
	// Override CSP for login page to allow inline styles and scripts
	c.Header("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'")
	
	if err := h.loginTemplate.Execute(c.Writer, data); err != nil {
		h.logger.Error("Failed to render login template", zap.Error(err))
		// Fallback to JSON response
		c.JSON(http.StatusOK, gin.H{
			"token":       token,
			"redirect_url": redirectURL,
			"user_id":     userID,
			"app_id":      appID,
			"message":     "Login successful - template render failed",
		})
	}
}

// renderLoginError renders an error page or JSON error response
func (h *AuthHandler) renderLoginError(c *gin.Context, message string, isJSON bool) {
	if isJSON {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":   "Bad Request",
			"message": message,
		})
		return
	}

	// Simple HTML error page
	c.Header("Content-Type", "text/html; charset=utf-8")
	// Override CSP for error page to allow inline styles
	c.Header("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'")
	c.String(http.StatusBadRequest, `
<!DOCTYPE html>
<html>
<head>
    <title>Login Error</title>
    <style>
        body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
        .error { background: #f8d7da; color: #721c24; padding: 15px; border-radius: 5px; }
    </style>
</head>
<body>
    <h1>Login Error</h1>
    <div class="error">%s</div>
    <p><a href="javascript:history.back()">Go back</a></p>
</body>
</html>`, message)
}

// generateSecureState generates a secure state parameter for OAuth flows
func (h *AuthHandler) generateSecureState(userID, appID string) string {
	// Generate random bytes for state
	stateBytes := make([]byte, 16)
	if _, err := rand.Read(stateBytes); err != nil {
		h.logger.Error("Failed to generate random state", zap.Error(err))
		// Fallback to less secure but functional state
		return fmt.Sprintf("state_%s_%s_%d", userID, appID, time.Now().UnixNano())
	}
	
	// Create HMAC signature to prevent tampering
	stateData := fmt.Sprintf("%s:%s:%x", userID, appID, stateBytes)
	mac := hmac.New(sha256.New, []byte(h.config.GetString("AUTH_SIGNING_KEY")))
	mac.Write([]byte(stateData))
	signature := hex.EncodeToString(mac.Sum(nil))
	
	// Return base64-encoded state with signature
	return hex.EncodeToString([]byte(fmt.Sprintf("%s.%s", stateData, signature)))
}

// Verify handles POST /verify
func (h *AuthHandler) Verify(c *gin.Context) {
	var req domain.VerifyRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		h.logger.Warn("Invalid verify request", zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{
			"error":   "Bad Request",
			"message": "Invalid request body: " + err.Error(),
		})
		return
	}

	h.logger.Debug("Verifying token", zap.String("app_id", req.AppID))

	response, err := h.tokenService.VerifyToken(c.Request.Context(), &req)
	if err != nil {
		h.logger.Error("Failed to verify token", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{
			"error":   "Internal Server Error",
			"message": "Failed to verify token",
		})
		return
	}

	c.JSON(http.StatusOK, response)
}

// Renew handles POST /renew
func (h *AuthHandler) Renew(c *gin.Context) {
	var req domain.RenewRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		h.logger.Warn("Invalid renew request", zap.Error(err))
		c.JSON(http.StatusBadRequest, gin.H{
			"error":   "Bad Request",
			"message": "Invalid request body: " + err.Error(),
		})
		return
	}

	h.logger.Info("Renewing token", zap.String("app_id", req.AppID), zap.String("user_id", req.UserID))

	response, err := h.tokenService.RenewUserToken(c.Request.Context(), &req)
	if err != nil {
		h.logger.Error("Failed to renew token", zap.Error(err))
		c.JSON(http.StatusInternalServerError, gin.H{
			"error":   "Internal Server Error",
			"message": "Failed to renew token",
		})
		return
	}

	c.JSON(http.StatusOK, response)
}
