package server

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"github.com/gin-gonic/gin"

	"warpbox/lib/boxstore"
	"warpbox/lib/config"
	"warpbox/lib/models"
)

const oneTimeTestBoxID = "0123456789abcdef0123456789abcdef"

func TestOneTimeDownloadNotReadyDoesNotConsume(t *testing.T) {
	app := setupOneTimeDownloadTest(t, false)
	writeOneTimeManifest(t, models.FileStatusWork, false)

	response := performOneTimeDownload(app)
	if response.Code != http.StatusConflict {
		t.Fatalf("expected not-ready download to return 409, got %d", response.Code)
	}

	manifest, err := boxstore.ReadManifest(oneTimeTestBoxID)
	if err != nil {
		t.Fatalf("ReadManifest returned error: %v", err)
	}
	if manifest.Consumed {
		t.Fatal("expected not-ready box to remain unconsumed")
	}
}

func TestOneTimeDownloadReadyConsumesAndDeletes(t *testing.T) {
	app := setupOneTimeDownloadTest(t, false)
	writeOneTimeManifest(t, models.FileStatusReady, true)

	response := performOneTimeDownload(app)
	if response.Code != http.StatusOK {
		t.Fatalf("expected ready download to return 200, got %d", response.Code)
	}
	if _, err := zip.NewReader(bytes.NewReader(response.Body.Bytes()), int64(response.Body.Len())); err != nil {
		t.Fatalf("expected valid zip body: %v", err)
	}
	if _, err := os.Stat(boxstore.BoxPath(oneTimeTestBoxID)); !os.IsNotExist(err) {
		t.Fatalf("expected consumed box to be deleted, stat err=%v", err)
	}
}

func TestOneTimeDownloadWriterFailureConsumesByDefault(t *testing.T) {
	app := setupOneTimeDownloadTest(t, false)
	writeOneTimeManifest(t, models.FileStatusReady, false)

	response := performOneTimeDownload(app)
	if response.Code != http.StatusInternalServerError {
		t.Fatalf("expected failed ZIP to return 500, got %d", response.Code)
	}
	if _, err := os.Stat(boxstore.BoxPath(oneTimeTestBoxID)); !os.IsNotExist(err) {
		t.Fatalf("expected failed ZIP to delete box by default, stat err=%v", err)
	}
}

func TestOneTimeDownloadWriterFailureCanRemainRetryable(t *testing.T) {
	app := setupOneTimeDownloadTest(t, true)
	writeOneTimeManifest(t, models.FileStatusReady, false)

	response := performOneTimeDownload(app)
	if response.Code != http.StatusInternalServerError {
		t.Fatalf("expected failed ZIP to return 500, got %d", response.Code)
	}

	manifest, err := boxstore.ReadManifest(oneTimeTestBoxID)
	if err != nil {
		t.Fatalf("ReadManifest returned error: %v", err)
	}
	if manifest.Consumed {
		t.Fatal("expected failed retryable ZIP to remain unconsumed")
	}
}

func TestOneTimeDownloadSecondAccessAfterConsumeIsGone(t *testing.T) {
	app := setupOneTimeDownloadTest(t, false)
	writeOneTimeManifest(t, models.FileStatusReady, true)

	manifest, err := boxstore.ReadManifest(oneTimeTestBoxID)
	if err != nil {
		t.Fatalf("ReadManifest returned error: %v", err)
	}
	manifest.Consumed = true
	if err := boxstore.WriteManifest(oneTimeTestBoxID, manifest); err != nil {
		t.Fatalf("WriteManifest returned error: %v", err)
	}

	response := performOneTimeDownload(app)
	if response.Code != http.StatusGone {
		t.Fatalf("expected consumed download to return 410, got %d", response.Code)
	}
}

func TestOneTimeStatusStripsThumbnailPath(t *testing.T) {
	app := setupOneTimeDownloadTest(t, false)
	app.config.APIEnabled = true
	writeOneTimeManifest(t, models.FileStatusReady, true)

	manifest, err := boxstore.ReadManifest(oneTimeTestBoxID)
	if err != nil {
		t.Fatalf("ReadManifest returned error: %v", err)
	}
	thumbnailPath := "/box/" + oneTimeTestBoxID + "/thumbnails/0123456789abcdef"
	manifest.Files[0].ThumbnailPath = &thumbnailPath
	manifest.Files[0].ThumbnailStatus = models.ThumbnailStatusReady
	if err := boxstore.WriteManifest(oneTimeTestBoxID, manifest); err != nil {
		t.Fatalf("WriteManifest returned error: %v", err)
	}

	response := performOneTimeStatus(app)
	if response.Code != http.StatusOK {
		t.Fatalf("expected status to return 200, got %d", response.Code)
	}
	var payload struct {
		Files []models.BoxFile `json:"files"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
		t.Fatalf("json.Unmarshal returned error: %v", err)
	}
	if len(payload.Files) != 1 {
		t.Fatalf("expected one file, got %#v", payload.Files)
	}
	if payload.Files[0].ThumbnailPath != nil {
		t.Fatalf("expected one-time status to strip thumbnail path, got %q", *payload.Files[0].ThumbnailPath)
	}
}

func TestRuntimeConfigAppliesDBOneTimeExpiryOverride(t *testing.T) {
	restoreExpiry := boxstore.OneTimeDownloadExpiry()
	defer boxstore.SetOneTimeDownloadExpiry(restoreExpiry)

	cfg, err := config.Load()
	if err != nil {
		t.Fatalf("Load returned error: %v", err)
	}
	if err := cfg.ApplyOverrides(map[string]string{config.SettingOneTimeDownloadExpirySecs: "42"}); err != nil {
		t.Fatalf("ApplyOverrides returned error: %v", err)
	}
	applyBoxstoreRuntimeConfig(cfg)

	if got := boxstore.OneTimeDownloadExpiry(); got != 42 {
		t.Fatalf("expected runtime one-time expiry to be updated from config, got %d", got)
	}
}

func setupOneTimeDownloadTest(t *testing.T, retryOnFailure bool) *App {
	t.Helper()
	gin.SetMode(gin.TestMode)

	restoreUploadRoot := boxstore.UploadRoot()
	t.Cleanup(func() { boxstore.SetUploadRoot(restoreUploadRoot) })
	boxstore.SetUploadRoot(t.TempDir())

	return &App{config: &config.Config{
		ZipDownloadsEnabled:           true,
		OneTimeDownloadRetryOnFailure: retryOnFailure,
	}}
}

func writeOneTimeManifest(t *testing.T, status string, createFile bool) {
	t.Helper()
	if err := os.MkdirAll(boxstore.BoxPath(oneTimeTestBoxID), 0755); err != nil {
		t.Fatalf("MkdirAll returned error: %v", err)
	}
	if createFile {
		path, ok := boxstore.SafeBoxFilePath(oneTimeTestBoxID, "file.txt")
		if !ok {
			t.Fatal("SafeBoxFilePath rejected test file")
		}
		if err := os.WriteFile(path, []byte("hello"), 0644); err != nil {
			t.Fatalf("WriteFile returned error: %v", err)
		}
	}

	manifest := models.BoxManifest{
		Files: []models.BoxFile{{
			ID:       "0123456789abcdef",
			Name:     "file.txt",
			Size:     5,
			MimeType: "text/plain",
			Status:   status,
		}},
		CreatedAt:       time.Now().UTC(),
		OneTimeDownload: true,
	}
	if err := boxstore.WriteManifest(oneTimeTestBoxID, manifest); err != nil {
		t.Fatalf("WriteManifest returned error: %v", err)
	}
}

func performOneTimeDownload(app *App) *httptest.ResponseRecorder {
	router := gin.New()
	router.GET("/box/:id/download", app.handleDownloadBox)
	request := httptest.NewRequest(http.MethodGet, "/box/"+oneTimeTestBoxID+"/download", nil)
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	return response
}

func performOneTimeStatus(app *App) *httptest.ResponseRecorder {
	router := gin.New()
	router.GET("/box/:id/status", app.handleBoxStatus)
	request := httptest.NewRequest(http.MethodGet, "/box/"+oneTimeTestBoxID+"/status", nil)
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	return response
}
