package handler

import (
	"context"
	"encoding/json"
	"errors"
	"log/slog"
	"net/http"
	"strings"
	"time"

	"github.com/flowy-live/llink/internal/auth"
	"github.com/flowy-live/llink/internal/depot"
	"github.com/flowy-live/llink/internal/human"
	"github.com/flowy-live/llink/internal/middleware"
	"github.com/flowy-live/llink/internal/network"
	"github.com/flowy-live/llink/internal/particle"
	"github.com/flowy-live/llink/internal/utils"
)

type Handler struct {
	authSvc     auth.AuthService
	humanSvc    human.Service
	networkSvc  network.Service
	particleSvc particle.Service
	depotSvc    depot.Service
}

func NewHandler(authSvc auth.AuthService, humanSvc human.Service, networkSvc network.Service, particleSvc particle.Service, depotSvc depot.Service) *Handler {
	return &Handler{
		authSvc:     authSvc,
		humanSvc:    humanSvc,
		networkSvc:  networkSvc,
		particleSvc: particleSvc,
		depotSvc:    depotSvc,
	}
}

// Response DTOs

type Human struct {
	// Id will be nil if this human is not registered
	Id          *string    `json:"id"`
	Email       string     `json:"email"`
	EmailPrefix string     `json:"email_prefix"`
	// CreatedAt will be nil if this human is not registered
	CreatedAt   *time.Time `json:"created_at"`
}

type Network struct {
	Id         string    `json:"id"`
	Name       string    `json:"name"`
	AdminHuman Human     `json:"admin_human"`
	Humans     []Human   `json:"humans"`
	CreatedAt  time.Time `json:"created_at"`
}

// Auth Request/Response DTOs

type RequestSignInCodeRequest struct {
	Email string `json:"email"`
}

type SignInRequest struct {
	Email string `json:"email"`
	Code  string `json:"code"`
}

type SignInResponse struct {
	Human Human  `json:"human"`
	Token string `json:"token"`
}

// Network Request DTOs

type CreateNetworkRequest struct {
	Name string `json:"name"`
}

type AddMembersToNetworkRequest struct {
	EmailAddresses []string `json:"email_addresses"`
}

type SetOpenStreamCapacityRequest struct {
	Capacity int `json:"capacity"`
}

type MembersRequest struct {
	Emails []string `json:"emails"`
}

// Depot DTOs

type PrepareUploadRequest struct {
	NetworkId     string `json:"network_id"`
	Name          string `json:"name"`
	ContentType   string `json:"content_type"`
	ContentLength int64  `json:"content_length"`
}

type PrepareUploadResponse struct {
	ObjectID      string            `json:"object_id"`
	UploadURL     string            `json:"upload_url"`
	UploadHeaders map[string]string `json:"upload_headers"`
}

type DepotObject struct {
	ID              string    `json:"id"`
	Name            string    `json:"name"`
	ContentType     string    `json:"content_type"`
	ContentLength   int64     `json:"content_length"`
	ContainsContent bool      `json:"contains_content"`
	DownloadURL     string    `json:"download_url,omitempty"`
	CreatedAt       time.Time `json:"created_at"`
}

// ============================================================================
// Auth Handlers
// ============================================================================

// RequestSignInCode creates a human account if not already existent and sends a sign-in code
func (h *Handler) RequestSignInCode(w http.ResponseWriter, r *http.Request) {
	var req RequestSignInCodeRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}

	if req.Email == "" {
		http.Error(w, "email is required", http.StatusBadRequest)
		return
	}

	// Auto-create human if doesn't exist
	_, err := h.humanSvc.GetOrCreateByEmail(r.Context(), req.Email)
	if err != nil {
		slog.Error("failed to get or create human", "error", err, "email", req.Email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	// Request sign-in code
	if err := h.authSvc.RequestSignInCode(r.Context(), req.Email); err != nil {
		slog.Error("failed to request sign-in code", "error", err, "email", req.Email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// SignIn verifies the code and returns a session token
func (h *Handler) SignIn(w http.ResponseWriter, r *http.Request) {
	var req SignInRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}

	if req.Email == "" || req.Code == "" {
		http.Error(w, "email and code are required", http.StatusBadRequest)
		return
	}

	token, err := h.authSvc.VerifySignInCode(r.Context(), req.Email, req.Code)
	if err != nil {
		if errors.Is(err, auth.ErrInvalidCode) {
			http.Error(w, "invalid code", http.StatusUnauthorized)
			return
		}
		slog.Error("failed to verify sign-in code", "error", err, "email", req.Email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	// Get the human
	hum, err := h.humanSvc.GetByEmail(r.Context(), req.Email)
	if err != nil {
		slog.Error("failed to get human after sign-in", "error", err, "email", req.Email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	resp := SignInResponse{
		Human: humanToDTO(hum),
		Token: token,
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// SignOut deletes the session from the token in headers
func (h *Handler) SignOut(w http.ResponseWriter, r *http.Request) {
	token := extractBearerToken(r)
	if token == "" {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	if err := h.authSvc.SignOut(r.Context(), token); err != nil {
		slog.Error("failed to sign out", "error", err)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// GetCurrentHuman returns the authenticated human
func (h *Handler) GetCurrentHuman(w http.ResponseWriter, r *http.Request) {
	email, ok := middleware.EmailFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	hum, err := h.humanSvc.GetByEmail(r.Context(), email)
	if err != nil {
		if errors.Is(err, human.ErrNotFound) {
			http.Error(w, "human not found", http.StatusNotFound)
			return
		}
		slog.Error("failed to get current human", "error", err, "email", email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	dto := humanToDTO(hum)
	json.NewEncoder(w).Encode(dto)
}

// ============================================================================
// Network Handlers
// ============================================================================

// CreateNetwork creates a new network
func (h *Handler) CreateNetwork(w http.ResponseWriter, r *http.Request) {
	email, ok := middleware.EmailFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	var req CreateNetworkRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}

	net, err := h.networkSvc.Create(r.Context(), req.Name, email)
	if err != nil {
		if errors.Is(err, network.ErrInvalidName) {
			http.Error(w, "name cannot be empty", http.StatusBadRequest)
			return
		}
		slog.Error("failed to create network", "error", err, "email", email, "name", req.Name)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	resp, err := h.networkToDTO(r.Context(), net)
	if err != nil {
		slog.Error("failed to convert network to DTO", "error", err, "network_id", net.ID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(resp)
}

// ListNetworks retrieves networks for the authenticated human
func (h *Handler) ListNetworks(w http.ResponseWriter, r *http.Request) {
	email, ok := middleware.EmailFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	networks, err := h.networkSvc.ListForEmail(r.Context(), email)
	if err != nil {
		slog.Error("failed to list networks", "error", err, "email", email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	resp := make([]Network, 0, len(networks))
	for _, net := range networks {
		dto, err := h.networkToDTO(r.Context(), net)
		if err != nil {
			slog.Warn("failed to convert network to DTO in list", "error", err, "network_id", net.ID)
			continue
		}
		resp = append(resp, dto)
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// GetNetwork retrieves a specific network
func (h *Handler) GetNetwork(w http.ResponseWriter, r *http.Request) {
	email, ok := middleware.EmailFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	networkID := r.PathValue("id")
	if networkID == "" {
		http.Error(w, "network id is required", http.StatusBadRequest)
		return
	}

	// Check membership
	isMember, err := h.networkSvc.IsMember(r.Context(), networkID, email)
	if err != nil {
		slog.Error("failed to check network membership", "error", err, "network_id", networkID, "email", email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	if !isMember {
		http.Error(w, "access denied", http.StatusForbidden)
		return
	}

	net, err := h.networkSvc.GetByID(r.Context(), networkID)
	if err != nil {
		if errors.Is(err, network.ErrNotFound) {
			http.Error(w, "network not found", http.StatusNotFound)
			return
		}
		slog.Error("failed to get network", "error", err, "network_id", networkID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	resp, err := h.networkToDTO(r.Context(), net)
	if err != nil {
		slog.Error("failed to convert network to DTO", "error", err, "network_id", networkID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// AddMembersToNetwork adds members to a network
func (h *Handler) AddMembersToNetwork(w http.ResponseWriter, r *http.Request) {
	email, ok := middleware.EmailFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	networkID := r.PathValue("id")
	if networkID == "" {
		http.Error(w, "network id is required", http.StatusBadRequest)
		return
	}

	// Check membership
	isMember, err := h.networkSvc.IsMember(r.Context(), networkID, email)
	if err != nil {
		slog.Error("failed to check network membership", "error", err, "network_id", networkID, "email", email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	if !isMember {
		http.Error(w, "access denied", http.StatusForbidden)
		return
	}

	var req AddMembersToNetworkRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}

	if len(req.EmailAddresses) == 0 {
		http.Error(w, "email addresses are required", http.StatusBadRequest)
		return
	}

	if err := h.networkSvc.AddMembers(r.Context(), networkID, req.EmailAddresses); err != nil {
		slog.Error("failed to add members to network", "error", err, "network_id", networkID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	// Return updated network
	net, err := h.networkSvc.GetByID(r.Context(), networkID)
	if err != nil {
		slog.Error("failed to get network after adding members", "error", err, "network_id", networkID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	resp, err := h.networkToDTO(r.Context(), net)
	if err != nil {
		slog.Error("failed to convert network to DTO", "error", err, "network_id", networkID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// RemoveMemberFromNetwork removes a member from a network
func (h *Handler) RemoveMemberFromNetwork(w http.ResponseWriter, r *http.Request) {
	email, ok := middleware.EmailFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	networkID := r.PathValue("id")
	memberEmail := r.PathValue("email")
	if networkID == "" || memberEmail == "" {
		http.Error(w, "network id and member email are required", http.StatusBadRequest)
		return
	}

	// Check membership
	isMember, err := h.networkSvc.IsMember(r.Context(), networkID, email)
	if err != nil {
		slog.Error("failed to check network membership", "error", err, "network_id", networkID, "email", email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	if !isMember {
		http.Error(w, "access denied", http.StatusForbidden)
		return
	}

	if err := h.networkSvc.RemoveMember(r.Context(), networkID, memberEmail); err != nil {
		slog.Error("failed to remove member from network", "error", err, "network_id", networkID, "member_email", memberEmail)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// DownloadParticleMedia returns a fresh signed download URL for media/file particles
func (h *Handler) DownloadParticleMedia(w http.ResponseWriter, r *http.Request) {
	_, ok := middleware.EmailFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	// TODO: integrate firebase to fetch particle, and verify visibility for this particle's media

	objectID := r.PathValue("id")

	downloadURL, err := h.depotSvc.GetDownloadURL(r.Context(), objectID)
	if err != nil {
		slog.Error("failed to get download URL", "error", err, "object_id", objectID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]string{"url": downloadURL})
}

// ============================================================================
// Depot Handlers
// ============================================================================

// PrepareUpload prepares an upload and returns a signed URL for direct upload to GCS
func (h *Handler) PrepareUpload(w http.ResponseWriter, r *http.Request) {
	email, ok := middleware.EmailFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	var req PrepareUploadRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid request body", http.StatusBadRequest)
		return
	}

	if req.NetworkId == "" {
		http.Error(w, "network_id is required", http.StatusBadRequest)
		return
	}

	// Check network membership
	isMember, err := h.networkSvc.IsMember(r.Context(), req.NetworkId, email)
	if err != nil {
		slog.Error("failed to check network membership", "error", err, "network_id", req.NetworkId, "email", email)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}
	if !isMember {
		http.Error(w, "access denied", http.StatusForbidden)
		return
	}

	input := depot.PrepareUploadInput{
		Prefix:        req.NetworkId,
		Name:          req.Name,
		ContentType:   req.ContentType,
		ContentLength: req.ContentLength,
	}

	result, err := h.depotSvc.PrepareUpload(r.Context(), input)
	if err != nil {
		if errors.Is(err, depot.ErrInvalidInput) {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		slog.Error("failed to prepare upload", "error", err, "network_id", req.NetworkId, "name", req.Name)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	resp := PrepareUploadResponse{
		ObjectID:      result.ObjectID,
		UploadURL:     result.UploadURL,
		UploadHeaders: result.UploadHeaders,
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

// ConfirmUpload confirms that an upload has been completed
func (h *Handler) ConfirmUpload(w http.ResponseWriter, r *http.Request) {
	_, ok := middleware.EmailFromContext(r.Context())
	if !ok {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	objectID := r.PathValue("id")
	if objectID == "" {
		http.Error(w, "object id is required", http.StatusBadRequest)
		return
	}

	obj, err := h.depotSvc.ConfirmUpload(r.Context(), objectID)
	if err != nil {
		if errors.Is(err, depot.ErrNotFound) {
			http.Error(w, "object not found", http.StatusNotFound)
			return
		}
		if errors.Is(err, depot.ErrInvalidInput) {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		slog.Error("failed to confirm upload", "error", err, "object_id", objectID)
		http.Error(w, "internal server error", http.StatusInternalServerError)
		return
	}

	resp := DepotObject{
		ID:              obj.ID,
		Name:            obj.Name,
		ContentType:     obj.ContentType,
		ContentLength:   obj.ContentLength,
		ContainsContent: obj.ContainsContent,
		DownloadURL:     "",
		CreatedAt:       obj.CreatedAt,
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(resp)
}

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

func humanToDTO(h *human.Human) Human {
	return Human{
		Id:          utils.CreateOptionalString(h.ID),
		Email:       h.Email,
		EmailPrefix: h.EmailPrefix,
		CreatedAt:   &h.CreatedAt,
	}
}

func emailPrefix(email string) string {
	return strings.Split(email, "@")[0]
}

func (h *Handler) networkToDTO(ctx context.Context, n *network.Network) (Network, error) {
	adminHuman, err := h.humanSvc.GetByEmail(ctx, n.AdminEmail)
	if err != nil {
		return Network{}, err
	}

	// Get member humans
	humans := make([]Human, 0, len(n.MemberEmails))
	for _, email := range n.MemberEmails {
		hum, err := h.humanSvc.GetByEmail(ctx, email)
		if err != nil {
			if err == human.ErrNotFound {
				humans = append(humans, Human{
					Id:          nil,
					CreatedAt:   nil,
					Email:       email,
					EmailPrefix: emailPrefix(email),
				})
			}
			continue
		} else {
			humans = append(humans, humanToDTO(hum))
		}
	}

	return Network{
		Id:         n.ID,
		Name:       n.Name,
		AdminHuman: humanToDTO(adminHuman),
		Humans:     humans,
		CreatedAt:  n.CreatedAt,
	}, nil
}

func extractBearerToken(r *http.Request) string {
	authHeader := r.Header.Get("Authorization")
	if authHeader == "" {
		return ""
	}

	const prefix = "Bearer "
	if len(authHeader) > len(prefix) && authHeader[:len(prefix)] == prefix {
		return authHeader[len(prefix):]
	}
	return ""
}
