package handlers

import (
	"context"
	"net/http"
	"strings"

	"github.com/breakpilot/consent-service/internal/middleware"
	"github.com/breakpilot/consent-service/internal/services"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

// OAuthHandler handles OAuth 2.0 endpoints
type OAuthHandler struct {
	oauthService *services.OAuthService
	totpService  *services.TOTPService
	authService  *services.AuthService
}

// NewOAuthHandler creates a new OAuthHandler
func NewOAuthHandler(oauthService *services.OAuthService, totpService *services.TOTPService, authService *services.AuthService) *OAuthHandler {
	return &OAuthHandler{
		oauthService: oauthService,
		totpService:  totpService,
		authService:  authService,
	}
}

// ========================================
// OAuth 2.0 Authorization Code Flow
// ========================================

// Authorize handles the OAuth 2.0 authorization request
// GET /oauth/authorize
func (h *OAuthHandler) Authorize(c *gin.Context) {
	responseType := c.Query("response_type")
	clientID := c.Query("client_id")
	redirectURI := c.Query("redirect_uri")
	scope := c.Query("scope")
	state := c.Query("state")
	codeChallenge := c.Query("code_challenge")
	codeChallengeMethod := c.Query("code_challenge_method")

	// Validate response_type
	if responseType != "code" {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "unsupported_response_type",
			"error_description": "Only 'code' response_type is supported",
		})
		return
	}

	// Validate client
	ctx := context.Background()
	client, err := h.oauthService.ValidateClient(ctx, clientID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "invalid_client",
			"error_description": "Unknown or invalid client_id",
		})
		return
	}

	// Validate redirect_uri
	if err := h.oauthService.ValidateRedirectURI(client, redirectURI); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "invalid_request",
			"error_description": "Invalid redirect_uri",
		})
		return
	}

	// Validate scopes
	scopes, err := h.oauthService.ValidateScopes(client, scope)
	if err != nil {
		redirectWithError(c, redirectURI, "invalid_scope", "One or more requested scopes are invalid", state)
		return
	}

	// For public clients, PKCE is required
	if client.IsPublic && codeChallenge == "" {
		redirectWithError(c, redirectURI, "invalid_request", "PKCE code_challenge is required for public clients", state)
		return
	}

	// Get authenticated user
	userID, err := middleware.GetUserID(c)
	if err != nil || userID == uuid.Nil {
		// User not authenticated - redirect to login
		// Store authorization request in session and redirect to login
		c.JSON(http.StatusUnauthorized, gin.H{
			"error":             "login_required",
			"error_description": "User must be authenticated to authorize",
			"login_url":         "/auth/login",
		})
		return
	}

	// Generate authorization code
	code, err := h.oauthService.GenerateAuthorizationCode(
		ctx, client, userID, redirectURI, scopes, codeChallenge, codeChallengeMethod,
	)
	if err != nil {
		redirectWithError(c, redirectURI, "server_error", "Failed to generate authorization code", state)
		return
	}

	// Redirect with code
	redirectURL := redirectURI + "?code=" + code
	if state != "" {
		redirectURL += "&state=" + state
	}

	c.Redirect(http.StatusFound, redirectURL)
}

// Token handles the OAuth 2.0 token request
// POST /oauth/token
func (h *OAuthHandler) Token(c *gin.Context) {
	grantType := c.PostForm("grant_type")

	switch grantType {
	case "authorization_code":
		h.tokenAuthorizationCode(c)
	case "refresh_token":
		h.tokenRefreshToken(c)
	default:
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "unsupported_grant_type",
			"error_description": "Only 'authorization_code' and 'refresh_token' grant types are supported",
		})
	}
}

// tokenAuthorizationCode handles the authorization_code grant
func (h *OAuthHandler) tokenAuthorizationCode(c *gin.Context) {
	code := c.PostForm("code")
	clientID := c.PostForm("client_id")
	redirectURI := c.PostForm("redirect_uri")
	codeVerifier := c.PostForm("code_verifier")

	if code == "" || clientID == "" || redirectURI == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "invalid_request",
			"error_description": "Missing required parameters: code, client_id, redirect_uri",
		})
		return
	}

	// Validate client
	ctx := context.Background()
	client, err := h.oauthService.ValidateClient(ctx, clientID)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "invalid_client",
			"error_description": "Unknown or invalid client_id",
		})
		return
	}

	// For confidential clients, validate client_secret
	if !client.IsPublic {
		clientSecret := c.PostForm("client_secret")
		if err := h.oauthService.ValidateClientSecret(client, clientSecret); err != nil {
			c.JSON(http.StatusUnauthorized, gin.H{
				"error":             "invalid_client",
				"error_description": "Invalid client credentials",
			})
			return
		}
	}

	// Exchange authorization code for tokens
	tokenResponse, err := h.oauthService.ExchangeAuthorizationCode(ctx, code, clientID, redirectURI, codeVerifier)
	if err != nil {
		switch err {
		case services.ErrCodeExpired:
			c.JSON(http.StatusBadRequest, gin.H{
				"error":             "invalid_grant",
				"error_description": "Authorization code has expired",
			})
		case services.ErrCodeUsed:
			c.JSON(http.StatusBadRequest, gin.H{
				"error":             "invalid_grant",
				"error_description": "Authorization code has already been used",
			})
		case services.ErrPKCEVerifyFailed:
			c.JSON(http.StatusBadRequest, gin.H{
				"error":             "invalid_grant",
				"error_description": "PKCE verification failed",
			})
		default:
			c.JSON(http.StatusBadRequest, gin.H{
				"error":             "invalid_grant",
				"error_description": "Invalid authorization code",
			})
		}
		return
	}

	c.JSON(http.StatusOK, tokenResponse)
}

// tokenRefreshToken handles the refresh_token grant
func (h *OAuthHandler) tokenRefreshToken(c *gin.Context) {
	refreshToken := c.PostForm("refresh_token")
	clientID := c.PostForm("client_id")
	scope := c.PostForm("scope")

	if refreshToken == "" || clientID == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "invalid_request",
			"error_description": "Missing required parameters: refresh_token, client_id",
		})
		return
	}

	ctx := context.Background()

	// Refresh access token
	tokenResponse, err := h.oauthService.RefreshAccessToken(ctx, refreshToken, clientID, scope)
	if err != nil {
		switch err {
		case services.ErrInvalidScope:
			c.JSON(http.StatusBadRequest, gin.H{
				"error":             "invalid_scope",
				"error_description": "Requested scope exceeds original grant",
			})
		default:
			c.JSON(http.StatusBadRequest, gin.H{
				"error":             "invalid_grant",
				"error_description": "Invalid or expired refresh token",
			})
		}
		return
	}

	c.JSON(http.StatusOK, tokenResponse)
}

// Revoke handles token revocation
// POST /oauth/revoke
func (h *OAuthHandler) Revoke(c *gin.Context) {
	token := c.PostForm("token")
	tokenTypeHint := c.PostForm("token_type_hint")

	if token == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "invalid_request",
			"error_description": "Missing token parameter",
		})
		return
	}

	ctx := context.Background()
	_ = h.oauthService.RevokeToken(ctx, token, tokenTypeHint)

	// RFC 7009: Always return 200 OK
	c.Status(http.StatusOK)
}

// Introspect handles token introspection (for resource servers)
// POST /oauth/introspect
func (h *OAuthHandler) Introspect(c *gin.Context) {
	token := c.PostForm("token")

	if token == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"error":             "invalid_request",
			"error_description": "Missing token parameter",
		})
		return
	}

	ctx := context.Background()
	claims, err := h.oauthService.ValidateAccessToken(ctx, token)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{"active": false})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"active":    true,
		"sub":       (*claims)["sub"],
		"client_id": (*claims)["client_id"],
		"scope":     (*claims)["scope"],
		"exp":       (*claims)["exp"],
		"iat":       (*claims)["iat"],
		"iss":       (*claims)["iss"],
	})
}

// ========================================
// OAuth Client Management (Admin)
// ========================================

// AdminCreateClient creates a new OAuth client
// POST /admin/oauth/clients
func (h *OAuthHandler) AdminCreateClient(c *gin.Context) {
	var req struct {
		Name         string   `json:"name" binding:"required"`
		Description  string   `json:"description"`
		RedirectURIs []string `json:"redirect_uris" binding:"required"`
		Scopes       []string `json:"scopes"`
		GrantTypes   []string `json:"grant_types"`
		IsPublic     bool     `json:"is_public"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
		return
	}

	userID, _ := middleware.GetUserID(c)

	// Default scopes
	if len(req.Scopes) == 0 {
		req.Scopes = []string{"openid", "profile", "email"}
	}

	// Default grant types
	if len(req.GrantTypes) == 0 {
		req.GrantTypes = []string{"authorization_code", "refresh_token"}
	}

	ctx := context.Background()
	client, clientSecret, err := h.oauthService.CreateClient(
		ctx, req.Name, req.Description, req.RedirectURIs, req.Scopes, req.GrantTypes, req.IsPublic, &userID,
	)

	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create client"})
		return
	}

	response := gin.H{
		"client_id":     client.ClientID,
		"name":          client.Name,
		"redirect_uris": client.RedirectURIs,
		"scopes":        client.Scopes,
		"grant_types":   client.GrantTypes,
		"is_public":     client.IsPublic,
	}

	// Only show client_secret once for confidential clients
	if !client.IsPublic && clientSecret != "" {
		response["client_secret"] = clientSecret
		response["client_secret_warning"] = "Store this secret securely. It will not be shown again."
	}

	c.JSON(http.StatusCreated, response)
}

// AdminGetClients lists all OAuth clients
// GET /admin/oauth/clients
func (h *OAuthHandler) AdminGetClients(c *gin.Context) {
	// This would need a new method in OAuthService
	// For now, return a placeholder
	c.JSON(http.StatusOK, gin.H{
		"clients": []interface{}{},
		"message": "Client listing not yet implemented",
	})
}

// ========================================
// Helper Functions
// ========================================

func redirectWithError(c *gin.Context, redirectURI, errorCode, errorDescription, state string) {
	separator := "?"
	if strings.Contains(redirectURI, "?") {
		separator = "&"
	}

	redirectURL := redirectURI + separator + "error=" + errorCode + "&error_description=" + errorDescription
	if state != "" {
		redirectURL += "&state=" + state
	}

	c.Redirect(http.StatusFound, redirectURL)
}
