package handler

import (
	"baron-sso-backend/internal/service"
	"errors"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
)

type DevHandler struct {
	Hydra *service.HydraAdminService
}

func NewDevHandler() *DevHandler {
	return &DevHandler{
		Hydra: service.NewHydraAdminService(),
	}
}

type clientSummary struct {
	ID           string                 `json:"id"`
	Name         string                 `json:"name"`
	Type         string                 `json:"type"`
	Status       string                 `json:"status"`
	CreatedAt    *time.Time             `json:"createdAt,omitempty"`
	RedirectURIs []string               `json:"redirectUris"`
	Scopes       []string               `json:"scopes"`
	Metadata     map[string]interface{} `json:"metadata,omitempty"`
}

type clientListResponse struct {
	Items  []clientSummary `json:"items"`
	Limit  int             `json:"limit"`
	Offset int             `json:"offset"`
}

type clientDetailResponse struct {
	Client    clientSummary   `json:"client"`
	Endpoints clientEndpoints `json:"endpoints"`
}

type clientEndpoints struct {
	Discovery     string `json:"discovery"`
	Issuer        string `json:"issuer"`
	Authorization string `json:"authorization"`
	Token         string `json:"token"`
	UserInfo      string `json:"userinfo"`
}

type consentSummary struct {
	Subject         string   `json:"subject"`
	ClientID        string   `json:"clientId"`
	ClientName      string   `json:"clientName,omitempty"`
	GrantedScopes   []string `json:"grantedScopes"`
	AuthenticatedAt string   `json:"authenticatedAt,omitempty"`
}

type consentListResponse struct {
	Items []consentSummary `json:"items"`
}

type clientUpsertRequest struct {
	ID                      *string                 `json:"id"`
	Name                    *string                 `json:"name"`
	Type                    *string                 `json:"type"`
	Status                  *string                 `json:"status"`
	RedirectURIs            *[]string               `json:"redirectUris"`
	Scopes                  *[]string               `json:"scopes"`
	GrantTypes              *[]string               `json:"grantTypes"`
	ResponseTypes           *[]string               `json:"responseTypes"`
	TokenEndpointAuthMethod *string                 `json:"tokenEndpointAuthMethod"`
	Metadata                *map[string]interface{} `json:"metadata"`
}

func (h *DevHandler) ListClients(c *fiber.Ctx) error {
	limit := c.QueryInt("limit", 50)
	offset := c.QueryInt("offset", 0)
	if limit <= 0 {
		limit = 50
	}
	if offset < 0 {
		offset = 0
	}

	clients, err := h.Hydra.ListClients(c.Context(), limit, offset)
	if err != nil {
		if errors.Is(err, service.ErrHydraNotFound) {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "clients not found"})
		}
		errMsg := err.Error()
		if strings.Contains(errMsg, "connection refused") || strings.Contains(errMsg, "dial tcp") {
			return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
				"error": "Hydra service is unavailable. Please check if Ory Hydra is running.",
			})
		}
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": errMsg})
	}

	items := make([]clientSummary, 0, len(clients))
	for _, client := range clients {
		items = append(items, mapClientSummary(client))
	}

	return c.JSON(clientListResponse{
		Items:  items,
		Limit:  limit,
		Offset: offset,
	})
}

func (h *DevHandler) GetClient(c *fiber.Ctx) error {
	clientID := c.Params("id")
	if clientID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"})
	}

	client, err := h.Hydra.GetClient(c.Context(), clientID)
	if err != nil {
		if errors.Is(err, service.ErrHydraNotFound) {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
		}
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	summary := mapClientSummary(*client)
	return c.JSON(clientDetailResponse{
		Client: summary,
		Endpoints: clientEndpoints{
			Discovery:     strings.TrimRight(h.Hydra.PublicURL, "/") + "/.well-known/openid-configuration",
			Issuer:        h.Hydra.PublicURL,
			Authorization: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/auth",
			Token:         strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/token",
			UserInfo:      strings.TrimRight(h.Hydra.PublicURL, "/") + "/userinfo",
		},
	})
}

func (h *DevHandler) UpdateClientStatus(c *fiber.Ctx) error {
	clientID := c.Params("id")
	if clientID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"})
	}

	var req struct {
		Status string `json:"status"`
	}
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}

	status := strings.ToLower(strings.TrimSpace(req.Status))
	if status != "active" && status != "inactive" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "status must be active or inactive"})
	}

	updated, err := h.Hydra.PatchClientStatus(c.Context(), clientID, status)
	if err != nil {
		if errors.Is(err, service.ErrHydraNotFound) {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
		}
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	summary := mapClientSummary(*updated)
	return c.JSON(clientDetailResponse{
		Client: summary,
		Endpoints: clientEndpoints{
			Discovery:     strings.TrimRight(h.Hydra.PublicURL, "/") + "/.well-known/openid-configuration",
			Issuer:        h.Hydra.PublicURL,
			Authorization: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/auth",
			Token:         strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/token",
			UserInfo:      strings.TrimRight(h.Hydra.PublicURL, "/") + "/userinfo",
		},
	})
}

func (h *DevHandler) CreateClient(c *fiber.Ctx) error {
	var req clientUpsertRequest
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}

	clientID := strings.TrimSpace(valueOr(req.ID, ""))
	if clientID == "" {
		clientID = uuid.NewString()
	}

	name := strings.TrimSpace(valueOr(req.Name, ""))
	if name == "" {
		name = clientID
	}

	redirectURIs := derefSlice(req.RedirectURIs, nil)
	if len(redirectURIs) == 0 {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "redirectUris is required"})
	}

	scopes := derefSlice(req.Scopes, defaultClientScopes())
	grantTypes := derefSlice(req.GrantTypes, defaultGrantTypes())
	responseTypes := derefSlice(req.ResponseTypes, defaultResponseTypes())

	clientType := strings.ToLower(strings.TrimSpace(valueOr(req.Type, "confidential")))
	if clientType != "public" && clientType != "confidential" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "type must be public or confidential"})
	}

	status := strings.ToLower(strings.TrimSpace(valueOr(req.Status, "active")))
	if status != "active" && status != "inactive" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "status must be active or inactive"})
	}

	metadata := mergeMetadata(nil, req.Metadata)
	if metadata == nil {
		metadata = map[string]interface{}{}
	}
	metadata["status"] = status

	tokenAuthMethod := strings.TrimSpace(valueOr(req.TokenEndpointAuthMethod, ""))
	if tokenAuthMethod == "" {
		if clientType == "public" {
			tokenAuthMethod = "none"
		} else {
			tokenAuthMethod = "client_secret_basic"
		}
	}

	client := service.HydraClient{
		ClientID:                clientID,
		ClientName:              name,
		RedirectURIs:            redirectURIs,
		GrantTypes:              grantTypes,
		ResponseTypes:           responseTypes,
		Scope:                   strings.Join(scopes, " "),
		TokenEndpointAuthMethod: tokenAuthMethod,
		Metadata:                metadata,
	}

	created, err := h.Hydra.CreateClient(c.Context(), client)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	summary := mapClientSummary(*created)
	return c.Status(fiber.StatusCreated).JSON(clientDetailResponse{
		Client: summary,
		Endpoints: clientEndpoints{
			Discovery:     strings.TrimRight(h.Hydra.PublicURL, "/") + "/.well-known/openid-configuration",
			Issuer:        h.Hydra.PublicURL,
			Authorization: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/auth",
			Token:         strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/token",
			UserInfo:      strings.TrimRight(h.Hydra.PublicURL, "/") + "/userinfo",
		},
	})
}

func (h *DevHandler) UpdateClient(c *fiber.Ctx) error {
	clientID := strings.TrimSpace(c.Params("id"))
	if clientID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"})
	}

	var req clientUpsertRequest
	if err := c.BodyParser(&req); err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid request body"})
	}

	current, err := h.Hydra.GetClient(c.Context(), clientID)
	if err != nil {
		if errors.Is(err, service.ErrHydraNotFound) {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
		}
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	clientType := ""
	if req.Type != nil {
		clientType = strings.ToLower(strings.TrimSpace(*req.Type))
		if clientType != "public" && clientType != "confidential" {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "type must be public or confidential"})
		}
	}

	status := ""
	if req.Status != nil {
		status = strings.ToLower(strings.TrimSpace(*req.Status))
		if status != "active" && status != "inactive" {
			return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "status must be active or inactive"})
		}
	}

	tokenAuthMethod := strings.TrimSpace(valueOr(req.TokenEndpointAuthMethod, ""))
	if tokenAuthMethod == "" && clientType != "" {
		if clientType == "public" {
			tokenAuthMethod = "none"
		} else {
			tokenAuthMethod = "client_secret_basic"
		}
	}

	if req.RedirectURIs != nil && len(*req.RedirectURIs) == 0 {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "redirectUris cannot be empty"})
	}

	metadata := mergeMetadata(current.Metadata, req.Metadata)
	if status != "" {
		if metadata == nil {
			metadata = map[string]interface{}{}
		}
		metadata["status"] = status
	}

	updated := service.HydraClient{
		ClientID:                current.ClientID,
		ClientName:              valueOr(req.Name, current.ClientName),
		RedirectURIs:            derefSlice(req.RedirectURIs, current.RedirectURIs),
		GrantTypes:              derefSlice(req.GrantTypes, current.GrantTypes),
		ResponseTypes:           derefSlice(req.ResponseTypes, current.ResponseTypes),
		Scope:                   buildScope(valueOrSlice(req.Scopes, strings.Fields(current.Scope))),
		TokenEndpointAuthMethod: resolveTokenAuthMethod(tokenAuthMethod, current.TokenEndpointAuthMethod),
		Metadata:                metadata,
	}

	updatedClient, err := h.Hydra.UpdateClient(c.Context(), clientID, updated)
	if err != nil {
		if errors.Is(err, service.ErrHydraNotFound) {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
		}
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	summary := mapClientSummary(*updatedClient)
	return c.JSON(clientDetailResponse{
		Client: summary,
		Endpoints: clientEndpoints{
			Discovery:     strings.TrimRight(h.Hydra.PublicURL, "/") + "/.well-known/openid-configuration",
			Issuer:        h.Hydra.PublicURL,
			Authorization: strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/auth",
			Token:         strings.TrimRight(h.Hydra.PublicURL, "/") + "/oauth2/token",
			UserInfo:      strings.TrimRight(h.Hydra.PublicURL, "/") + "/userinfo",
		},
	})
}

func (h *DevHandler) DeleteClient(c *fiber.Ctx) error {
	clientID := strings.TrimSpace(c.Params("id"))
	if clientID == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "client id is required"})
	}

	if err := h.Hydra.DeleteClient(c.Context(), clientID); err != nil {
		if errors.Is(err, service.ErrHydraNotFound) {
			return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "client not found"})
		}
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	return c.SendStatus(fiber.StatusNoContent)
}

func (h *DevHandler) ListConsents(c *fiber.Ctx) error {
	subject := strings.TrimSpace(c.Query("subject"))
	if subject == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "subject is required"})
	}
	clientID := strings.TrimSpace(c.Query("client_id"))

	sessions, err := h.Hydra.ListConsentSessions(c.Context(), subject, clientID)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	items := make([]consentSummary, 0, len(sessions))
	for _, session := range sessions {
		authAt := ""
		if session.AuthenticatedAt != nil {
			authAt = session.AuthenticatedAt.Format(time.RFC3339)
		} else if session.RequestedAt != nil {
			authAt = session.RequestedAt.Format(time.RFC3339)
		}
		items = append(items, consentSummary{
			Subject:         session.Subject,
			ClientID:        session.Client.ClientID,
			ClientName:      session.Client.ClientName,
			GrantedScopes:   session.GrantedScope,
			AuthenticatedAt: authAt,
		})
	}

	return c.JSON(consentListResponse{Items: items})
}

func (h *DevHandler) RevokeConsents(c *fiber.Ctx) error {
	subject := strings.TrimSpace(c.Query("subject"))
	if subject == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "subject is required"})
	}
	clientID := strings.TrimSpace(c.Query("client_id"))

	if err := h.Hydra.RevokeConsentSessions(c.Context(), subject, clientID); err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
	}

	return c.SendStatus(fiber.StatusNoContent)
}

func mapClientSummary(client service.HydraClient) clientSummary {
	status := "active"
	if client.Metadata != nil {
		if value, ok := client.Metadata["status"].(string); ok && strings.ToLower(value) == "inactive" {
			status = "inactive"
		}
	}

	clientType := "confidential"
	if strings.EqualFold(client.TokenEndpointAuthMethod, "none") {
		clientType = "public"
	}

	name := strings.TrimSpace(client.ClientName)
	if name == "" {
		name = client.ClientID
	}

	scopes := strings.Fields(client.Scope)

	return clientSummary{
		ID:           client.ClientID,
		Name:         name,
		Type:         clientType,
		Status:       status,
		RedirectURIs: client.RedirectURIs,
		Scopes:       scopes,
		Metadata:     client.Metadata,
	}
}

func defaultClientScopes() []string {
	return []string{"openid", "profile", "email"}
}

func defaultGrantTypes() []string {
	return []string{"authorization_code", "refresh_token"}
}

func defaultResponseTypes() []string {
	return []string{"code"}
}

func buildScope(scopes []string) string {
	return strings.Join(scopes, " ")
}

func valueOr(ptr *string, fallback string) string {
	if ptr == nil {
		return fallback
	}
	return *ptr
}

func valueOrSlice(ptr *[]string, fallback []string) []string {
	if ptr == nil {
		return fallback
	}
	return *ptr
}

func derefSlice(ptr *[]string, fallback []string) []string {
	if ptr == nil {
		return fallback
	}
	return *ptr
}

func mergeMetadata(current map[string]interface{}, incoming *map[string]interface{}) map[string]interface{} {
	if incoming == nil {
		return current
	}
	merged := map[string]interface{}{}
	for k, v := range current {
		merged[k] = v
	}
	for k, v := range *incoming {
		merged[k] = v
	}
	return merged
}

func resolveTokenAuthMethod(requested, fallback string) string {
	if strings.TrimSpace(requested) == "" {
		return fallback
	}
	return requested
}
