package handlers

import (
	"fmt"
	"net/http"
	"os"

	"warpbox.dev/backend/libs/helpers"
	"warpbox.dev/backend/libs/services"
	"warpbox.dev/backend/libs/web"
)

type dashboardData struct {
	User          services.PublicUser
	Collections   []collectionView
	Boxes         []userBoxView
	StorageUsed   string
	MaxUploadSize string
	Selected      string
	LastInviteURL string
}

type collectionView struct {
	ID   string
	Name string
}

type userBoxView struct {
	ID             string
	Title          string
	CollectionID   string
	CollectionName string
	FileCount      int
	Size           string
	CreatedAt      string
	ExpiresAt      string
	URL            string
}

func (a *App) Dashboard(w http.ResponseWriter, r *http.Request) {
	user, ok := a.requireUser(w, r)
	if !ok {
		return
	}
	a.logger.Info("user dashboard viewed", withRequestLogAttrs(r,
		"source", "page",
		"severity", "user_activity",
		"code", 2502,
		"user_id", user.ID,
	)...)
	collections, err := a.authService.ListCollections(user.ID)
	if err != nil {
		http.Error(w, "unable to load collections", http.StatusInternalServerError)
		return
	}
	collectionNames := map[string]string{}
	collectionViews := make([]collectionView, 0, len(collections))
	for _, collection := range collections {
		collectionNames[collection.ID] = collection.Name
		collectionViews = append(collectionViews, collectionView{ID: collection.ID, Name: collection.Name})
	}
	boxes, err := a.uploadService.UserBoxes(user.ID, collectionNames)
	if err != nil {
		http.Error(w, "unable to load boxes", http.StatusInternalServerError)
		return
	}
	storageUsed, err := a.uploadService.UserStorageUsed(user.ID)
	if err != nil {
		http.Error(w, "unable to load storage usage", http.StatusInternalServerError)
		return
	}

	selected := r.URL.Query().Get("collection")
	boxViews := make([]userBoxView, 0, len(boxes))
	for _, row := range boxes {
		if selected != "" && row.Box.CollectionID != selected {
			continue
		}
		title := row.Box.Title
		if title == "" {
			title = fmt.Sprintf("%d file upload", len(row.Box.Files))
		}
		boxViews = append(boxViews, userBoxView{
			ID:             row.Box.ID,
			Title:          title,
			CollectionID:   row.Box.CollectionID,
			CollectionName: row.CollectionName,
			FileCount:      len(row.Box.Files),
			Size:           row.TotalSizeLabel,
			CreatedAt:      row.Box.CreatedAt.Format("Jan 2 15:04"),
			ExpiresAt:      boxExpiryLabel(row.Box.ExpiresAt, "Jan 2 15:04"),
			URL:            "/d/" + row.Box.ID,
		})
	}

	a.renderPage(w, r, http.StatusOK, "dashboard.html", web.PageData{
		Title:       "My files",
		Description: "Your Warpbox personal file space.",
		CurrentUser: a.authService.PublicUser(user),
		Data: dashboardData{
			User:          a.authService.PublicUser(user),
			Collections:   collectionViews,
			Boxes:         boxViews,
			StorageUsed:   helpers.FormatBytes(storageUsed),
			MaxUploadSize: a.uploadService.MaxUploadSizeLabel(),
			Selected:      selected,
		},
	})
}

func (a *App) CreateCollection(w http.ResponseWriter, r *http.Request) {
	user, ok := a.requireUser(w, r)
	if !ok || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/app", http.StatusSeeOther)
		return
	}
	if _, err := a.authService.CreateCollection(user.ID, r.FormValue("name")); err != nil {
		a.logger.Warn("collection create failed", withRequestLogAttrs(r, "source", "user_activity", "severity", "warn", "code", 4410, "user_id", user.ID, "error", err.Error())...)
	} else {
		a.logger.Info("collection created", withRequestLogAttrs(r, "source", "user_activity", "severity", "user_activity", "code", 2410, "user_id", user.ID, "name", r.FormValue("name"))...)
	}
	http.Redirect(w, r, "/app", http.StatusSeeOther)
}

func (a *App) RenameUserBox(w http.ResponseWriter, r *http.Request) {
	user, ok := a.requireUser(w, r)
	if !ok || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/app", http.StatusSeeOther)
		return
	}
	if err := a.uploadService.RenameOwnedBox(r.PathValue("boxID"), user.ID, r.FormValue("title")); err != nil {
		a.logger.Warn("owned box rename failed", withRequestLogAttrs(r, "source", "user_activity", "severity", "warn", "code", 4411, "user_id", user.ID, "box_id", r.PathValue("boxID"), "error", err.Error())...)
		a.handleUserBoxError(w, r, err)
		return
	}
	a.logger.Info("owned box renamed", withRequestLogAttrs(r, "source", "user_activity", "severity", "user_activity", "code", 2411, "user_id", user.ID, "box_id", r.PathValue("boxID"))...)
	http.Redirect(w, r, "/app", http.StatusSeeOther)
}

func (a *App) MoveUserBox(w http.ResponseWriter, r *http.Request) {
	user, ok := a.requireUser(w, r)
	if !ok || !a.validateCSRF(w, r) {
		return
	}
	if err := r.ParseForm(); err != nil {
		http.Redirect(w, r, "/app", http.StatusSeeOther)
		return
	}
	collectionID := r.FormValue("collection_id")
	if !a.authService.CollectionOwnedBy(collectionID, user.ID) {
		a.logger.Warn("owned box move invalid collection", withRequestLogAttrs(r, "source", "user_activity", "severity", "warn", "code", 4412, "user_id", user.ID, "box_id", r.PathValue("boxID"), "collection_id", collectionID)...)
		http.Error(w, "collection not found", http.StatusForbidden)
		return
	}
	if err := a.uploadService.MoveOwnedBox(r.PathValue("boxID"), user.ID, collectionID); err != nil {
		a.logger.Warn("owned box move failed", withRequestLogAttrs(r, "source", "user_activity", "severity", "warn", "code", 4413, "user_id", user.ID, "box_id", r.PathValue("boxID"), "error", err.Error())...)
		a.handleUserBoxError(w, r, err)
		return
	}
	a.logger.Info("owned box moved", withRequestLogAttrs(r, "source", "user_activity", "severity", "user_activity", "code", 2412, "user_id", user.ID, "box_id", r.PathValue("boxID"), "collection_id", collectionID)...)
	http.Redirect(w, r, "/app", http.StatusSeeOther)
}

func (a *App) DeleteUserBox(w http.ResponseWriter, r *http.Request) {
	user, ok := a.requireUser(w, r)
	if !ok || !a.validateCSRF(w, r) {
		return
	}
	if err := a.uploadService.DeleteOwnedBox(r.PathValue("boxID"), user.ID); err != nil {
		a.logger.Warn("owned box delete failed", withRequestLogAttrs(r, "source", "user_activity", "severity", "warn", "code", 4414, "user_id", user.ID, "box_id", r.PathValue("boxID"), "error", err.Error())...)
		a.handleUserBoxError(w, r, err)
		return
	}
	a.logger.Info("owned box deleted", withRequestLogAttrs(r, "source", "user_activity", "severity", "user_activity", "code", 2413, "user_id", user.ID, "box_id", r.PathValue("boxID"))...)
	http.Redirect(w, r, "/app", http.StatusSeeOther)
}

func (a *App) handleUserBoxError(w http.ResponseWriter, r *http.Request, err error) {
	if os.IsPermission(err) {
		http.Error(w, "not allowed", http.StatusForbidden)
		return
	}
	if os.IsNotExist(err) {
		http.NotFound(w, r)
		return
	}
	http.Error(w, "unable to update box", http.StatusInternalServerError)
}
