package server

import (
	"fmt"
	"net/http"
	"sort"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"warpbox/lib/boxstore"
)

type adminBoxesActionRequest struct {
	Action       string   `json:"action"`
	BoxIDs       []string `json:"box_ids"`
	DeltaSeconds int64    `json:"delta_seconds,omitempty"`
}

type adminBoxFileView struct {
	Name         string `json:"name"`
	SizeLabel    string `json:"size_label"`
	MimeType     string `json:"mime_type"`
	Status       string `json:"status"`
	StatusLabel  string `json:"status_label"`
	DownloadPath string `json:"download_path"`
	ThumbnailURL string `json:"thumbnail_url"`
	IsComplete   bool   `json:"is_complete"`
}

type adminBoxView struct {
	ID                string             `json:"id"`
	Status            string             `json:"status"`
	StatusLabel       string             `json:"status_label"`
	FileCount         int                `json:"file_count"`
	CompleteFiles     int                `json:"complete_files"`
	PendingFiles      int                `json:"pending_files"`
	FailedFiles       int                `json:"failed_files"`
	TotalSizeLabel    string             `json:"total_size_label"`
	CreatedAtLabel    string             `json:"created_at_label"`
	CreatedAtISO      string             `json:"created_at_iso"`
	ExpiresAtLabel    string             `json:"expires_at_label"`
	ExpiresAtISO      string             `json:"expires_at_iso"`
	RetentionLabel    string             `json:"retention_label"`
	PasswordProtected bool               `json:"password_protected"`
	OneTimeDownload   bool               `json:"one_time_download"`
	ZipDisabled       bool               `json:"zip_disabled"`
	ZipAvailable      bool               `json:"zip_available"`
	Consumed          bool               `json:"consumed"`
	HasManifest       bool               `json:"has_manifest"`
	OpenURL           string             `json:"open_url"`
	ZipURL            string             `json:"zip_url"`
	Flags             []string           `json:"flags"`
	Files             []adminBoxFileView `json:"files"`
	SearchText        string             `json:"search_text"`
}

func (app *App) handleAdminBoxes(ctx *gin.Context) {
	if !app.adminLoginEnabled() {
		ctx.Redirect(http.StatusSeeOther, "/")
		return
	}

	boxes, err := app.listAdminBoxes()
	if err != nil {
		ctx.String(http.StatusInternalServerError, "Could not load boxes")
		return
	}

	ctx.HTML(http.StatusOK, "admin/boxes.html", gin.H{
		"AdminUsername":  app.config.AdminUsername,
		"AdminEmail":     app.config.AdminEmail,
		"ActivePage":     "boxes",
		"Boxes":          boxes,
		"ZipDownloadsOn": app.config.ZipDownloadsEnabled,
	})
}

func (app *App) handleAdminBoxesAction(ctx *gin.Context) {
	var request adminBoxesActionRequest
	if err := ctx.ShouldBindJSON(&request); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid action payload"})
		return
	}

	if len(request.BoxIDs) == 0 {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Select one or more boxes first"})
		return
	}

	switch request.Action {
	case "delete", "expire", "bump":
	default:
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Unknown action"})
		return
	}

	if request.Action == "bump" && request.DeltaSeconds <= 0 {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Missing bump duration"})
		return
	}

	processed := 0
	warnings := make([]string, 0)

	for _, boxID := range request.BoxIDs {
		if !boxstore.ValidBoxID(boxID) {
			warnings = append(warnings, fmt.Sprintf("%s: invalid box id", boxID))
			continue
		}

		var err error
		switch request.Action {
		case "delete":
			err = boxstore.DeleteBox(boxID)
		case "expire":
			_, err = boxstore.ExpireBox(boxID)
		case "bump":
			_, err = boxstore.BumpBoxExpiry(boxID, time.Duration(request.DeltaSeconds)*time.Second)
		}

		if err != nil {
			warnings = append(warnings, fmt.Sprintf("%s: %v", boxID, err))
			continue
		}
		processed++
	}

	boxes, err := app.listAdminBoxes()
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Action finished, but boxes could not be reloaded"})
		return
	}

	status := http.StatusOK
	if processed == 0 && len(warnings) > 0 {
		status = http.StatusBadRequest
	}

	ctx.JSON(status, gin.H{
		"ok":       len(warnings) == 0,
		"message":  adminBoxesActionMessage(request.Action, processed, request.DeltaSeconds),
		"warnings": warnings,
		"boxes":    boxes,
	})
}

func (app *App) listAdminBoxes() ([]adminBoxView, error) {
	summaries, err := boxstore.ListBoxSummaries()
	if err != nil {
		return nil, err
	}

	boxes := make([]adminBoxView, 0, len(summaries))
	for _, summary := range summaries {
		boxView, err := app.buildAdminBoxView(summary.ID)
		if err != nil {
			continue
		}
		boxes = append(boxes, boxView)
	}

	sort.Slice(boxes, func(i, j int) bool {
		return boxes[i].CreatedAtISO > boxes[j].CreatedAtISO
	})
	return boxes, nil
}

func (app *App) buildAdminBoxView(boxID string) (adminBoxView, error) {
	summary, err := boxstore.BoxSummary(boxID)
	if err != nil {
		return adminBoxView{}, err
	}

	files, err := boxstore.ListFiles(boxID)
	if err != nil {
		return adminBoxView{}, err
	}

	manifest, manifestErr := boxstore.ReadManifest(boxID)
	hasManifest := manifestErr == nil

	boxView := adminBoxView{
		ID:                summary.ID,
		FileCount:         summary.FileCount,
		TotalSizeLabel:    summary.TotalSizeLabel,
		CreatedAtLabel:    adminTimeLabel(summary.CreatedAt),
		CreatedAtISO:      formatBrowserTime(summary.CreatedAt),
		ExpiresAtLabel:    "Not set",
		ExpiresAtISO:      formatBrowserTime(summary.ExpiresAt),
		RetentionLabel:    "Legacy / unmanaged",
		PasswordProtected: summary.PasswordProtected,
		OneTimeDownload:   summary.OneTimeDownload,
		HasManifest:       hasManifest,
		OpenURL:           "/box/" + summary.ID,
		Files:             make([]adminBoxFileView, 0, len(files)),
	}

	if !summary.ExpiresAt.IsZero() {
		boxView.ExpiresAtLabel = adminTimeLabel(summary.ExpiresAt)
	}

	searchParts := []string{summary.ID, summary.TotalSizeLabel}
	for _, file := range files {
		if file.IsComplete {
			boxView.CompleteFiles++
		}
		if file.Status == "failed" {
			boxView.FailedFiles++
		}
		if !file.IsComplete && file.Status != "failed" {
			boxView.PendingFiles++
		}

		boxView.Files = append(boxView.Files, adminBoxFileView{
			Name:         file.Name,
			SizeLabel:    file.SizeLabel,
			MimeType:     file.MimeType,
			Status:       file.Status,
			StatusLabel:  file.StatusLabel,
			DownloadPath: file.DownloadPath,
			ThumbnailURL: file.ThumbnailURL,
			IsComplete:   file.IsComplete,
		})
		searchParts = append(searchParts, file.Name, file.MimeType, file.StatusLabel)
	}

	if hasManifest {
		boxView.RetentionLabel = manifest.RetentionLabel
		boxView.ZipDisabled = manifest.DisableZip
		boxView.Consumed = manifest.Consumed
	} else {
		boxView.ZipDisabled = false
	}

	boxView.ZipAvailable = app.config.ZipDownloadsEnabled && !boxView.ZipDisabled && !boxView.Consumed && boxView.FileCount > 0 && boxView.PendingFiles == 0
	if boxView.ZipAvailable {
		boxView.ZipURL = "/box/" + summary.ID + "/download"
	}

	boxView.Status, boxView.StatusLabel = deriveAdminBoxStatus(hasManifest, summary.Expired, boxView.PendingFiles, boxView.FailedFiles, boxView.Consumed)
	boxView.Flags = deriveAdminBoxFlags(boxView)
	searchParts = append(searchParts, boxView.StatusLabel, boxView.RetentionLabel)
	boxView.SearchText = strings.ToLower(strings.Join(searchParts, " "))

	return boxView, nil
}

func deriveAdminBoxStatus(hasManifest bool, expired bool, pendingFiles int, failedFiles int, consumed bool) (string, string) {
	switch {
	case !hasManifest:
		return "legacy", "Legacy"
	case consumed:
		return "consumed", "Consumed"
	case expired:
		return "expired", "Expired"
	case pendingFiles > 0:
		return "uploading", "Uploading"
	case failedFiles > 0:
		return "attention", "Needs review"
	default:
		return "ready", "Ready"
	}
}

func deriveAdminBoxFlags(box adminBoxView) []string {
	flags := make([]string, 0, 5)
	if box.PasswordProtected {
		flags = append(flags, "protected")
	}
	if box.OneTimeDownload {
		flags = append(flags, "one-time")
	}
	if box.ZipDisabled {
		flags = append(flags, "zip off")
	}
	if !box.HasManifest {
		flags = append(flags, "legacy")
	}
	if box.Consumed {
		flags = append(flags, "consumed")
	}
	return flags
}

func adminTimeLabel(value time.Time) string {
	if value.IsZero() {
		return "Not set"
	}
	return value.UTC().Format("2006-01-02 15:04 UTC")
}

func adminBoxesActionMessage(action string, processed int, deltaSeconds int64) string {
	switch action {
	case "delete":
		return fmt.Sprintf("Deleted %d box(es)", processed)
	case "expire":
		return fmt.Sprintf("Expired %d box(es)", processed)
	case "bump":
		return fmt.Sprintf("Extended %d box(es) by %s", processed, adminBoxesDeltaLabel(deltaSeconds))
	default:
		return "Action complete"
	}
}

func adminBoxesDeltaLabel(deltaSeconds int64) string {
	switch deltaSeconds {
	case 24 * 60 * 60:
		return "24h"
	case 7 * 24 * 60 * 60:
		return "7d"
	default:
		return (time.Duration(deltaSeconds) * time.Second).String()
	}
}
