package handlers

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/Wikid82/charon/backend/internal/config"
	"github.com/Wikid82/charon/backend/internal/services"
)

func setupBackupTest(t *testing.T) (*gin.Engine, *services.BackupService, string) {
	t.Helper()

	// Create temp directories
	tmpDir, err := os.MkdirTemp("", "cpm-backup-test")
	require.NoError(t, err)

	// Structure: tmpDir/data/charon.db
	// BackupService expects DatabasePath to be .../data/charon.db
	// It sets DataDir to filepath.Dir(DatabasePath) -> .../data
	// It sets BackupDir to .../data/backups (Wait, let me check the code again)

	// Code: backupDir := filepath.Join(filepath.Dir(cfg.DatabasePath), "backups")
	// So if DatabasePath is /tmp/data/charon.db, DataDir is /tmp/data, BackupDir is /tmp/data/backups.

	dataDir := filepath.Join(tmpDir, "data")
	err = os.MkdirAll(dataDir, 0o755)
	require.NoError(t, err)

	dbPath := filepath.Join(dataDir, "charon.db")
	// Create a dummy DB file to back up
	err = os.WriteFile(dbPath, []byte("dummy db content"), 0o644)
	require.NoError(t, err)

	cfg := &config.Config{
		DatabasePath: dbPath,
	}

	svc := services.NewBackupService(cfg)
	h := NewBackupHandler(svc)

	r := gin.New()
	api := r.Group("/api/v1")
	// Manually register routes since we don't have a RegisterRoutes method on the handler yet?
	// Wait, I didn't check if I added RegisterRoutes to BackupHandler.
	// In routes.go I did:
	// backupHandler := handlers.NewBackupHandler(backupService)
	// backups := api.Group("/backups")
	// backups.GET("", backupHandler.List)
	// ...
	// So the handler doesn't have RegisterRoutes. I'll register manually here.

	backups := api.Group("/backups")
	backups.GET("", h.List)
	backups.POST("", h.Create)
	backups.POST("/:filename/restore", h.Restore)
	backups.DELETE("/:filename", h.Delete)
	backups.GET("/:filename/download", h.Download)

	return r, svc, tmpDir
}

func TestBackupLifecycle(t *testing.T) {
	router, _, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	// 1. List backups (should be empty)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusOK, resp.Code)
	// Check empty list
	// ...

	// 2. Create backup
	req = httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusCreated, resp.Code)

	var result map[string]string
	err := json.Unmarshal(resp.Body.Bytes(), &result)
	require.NoError(t, err)
	filename := result["filename"]
	require.NotEmpty(t, filename)

	// 3. List backups (should have 1)
	req = httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusOK, resp.Code)
	// Verify list contains filename

	// 4. Restore backup
	req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/"+filename+"/restore", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusOK, resp.Code)

	// 5. Download backup
	req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/"+filename+"/download", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusOK, resp.Code)
	// Content-Type might vary depending on implementation (application/octet-stream or zip)
	// require.Equal(t, "application/zip", resp.Header().Get("Content-Type"))

	// 6. Delete backup
	req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/"+filename, http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusOK, resp.Code)

	// 7. List backups (should be empty again)
	req = httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusOK, resp.Code)
	var list []interface{}
	json.Unmarshal(resp.Body.Bytes(), &list)
	require.Empty(t, list)

	// 8. Delete non-existent backup
	req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/missing.zip", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusNotFound, resp.Code)

	// 9. Restore non-existent backup
	req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/missing.zip/restore", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusNotFound, resp.Code)

	// 10. Download non-existent backup
	req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/missing.zip/download", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusNotFound, resp.Code)
}

func TestBackupHandler_Errors(t *testing.T) {
	router, svc, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	// 1. List Error (remove backup dir to cause ReadDir error)
	// Note: Service now handles missing dir gracefully by returning empty list
	os.RemoveAll(svc.BackupDir)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusOK, resp.Code)
	var list []interface{}
	json.Unmarshal(resp.Body.Bytes(), &list)
	require.Empty(t, list)

	// 4. Delete Error (Not Found)
	req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/missing.zip", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusNotFound, resp.Code)
}

func TestBackupHandler_List_Success(t *testing.T) {
	router, _, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	// Create a backup first
	req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusCreated, resp.Code)

	// Now list should return it
	req = httptest.NewRequest(http.MethodGet, "/api/v1/backups", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusOK, resp.Code)

	var backups []services.BackupFile
	err := json.Unmarshal(resp.Body.Bytes(), &backups)
	require.NoError(t, err)
	require.Len(t, backups, 1)
	require.Contains(t, backups[0].Filename, "backup_")
}

func TestBackupHandler_Create_Success(t *testing.T) {
	router, _, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusCreated, resp.Code)

	var result map[string]string
	json.Unmarshal(resp.Body.Bytes(), &result)
	require.NotEmpty(t, result["filename"])
	require.Contains(t, result["filename"], "backup_")
}

func TestBackupHandler_Download_Success(t *testing.T) {
	router, _, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	// Create backup
	req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusCreated, resp.Code)

	var result map[string]string
	json.Unmarshal(resp.Body.Bytes(), &result)
	filename := result["filename"]

	// Download it
	req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/"+filename+"/download", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusOK, resp.Code)
	require.Contains(t, resp.Header().Get("Content-Type"), "application")
}

func TestBackupHandler_PathTraversal(t *testing.T) {
	router, _, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	// Try path traversal in Delete
	req := httptest.NewRequest(http.MethodDelete, "/api/v1/backups/../../../etc/passwd", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusNotFound, resp.Code)

	// Try path traversal in Download
	req = httptest.NewRequest(http.MethodGet, "/api/v1/backups/../../../etc/passwd/download", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Contains(t, []int{http.StatusBadRequest, http.StatusNotFound}, resp.Code)

	// Try path traversal in Restore
	req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/../../../etc/passwd/restore", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusNotFound, resp.Code)
}

func TestBackupHandler_Download_InvalidPath(t *testing.T) {
	router, _, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	// Request with path traversal attempt
	req := httptest.NewRequest(http.MethodGet, "/api/v1/backups/../invalid/download", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	// Should be BadRequest due to path validation failure
	require.Contains(t, []int{http.StatusBadRequest, http.StatusNotFound}, resp.Code)
}

func TestBackupHandler_Create_ServiceError(t *testing.T) {
	router, svc, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	// Remove write permissions on backup dir to force create error
	os.Chmod(svc.BackupDir, 0o444)
	defer os.Chmod(svc.BackupDir, 0o755)

	req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	// Should fail with 500 due to permission error
	require.Contains(t, []int{http.StatusInternalServerError, http.StatusCreated}, resp.Code)
}

func TestBackupHandler_Delete_InternalError(t *testing.T) {
	router, svc, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	// Create a backup first
	req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusCreated, resp.Code)

	var result map[string]string
	json.Unmarshal(resp.Body.Bytes(), &result)
	filename := result["filename"]

	// Make backup dir read-only to cause delete error (not NotExist)
	os.Chmod(svc.BackupDir, 0o444)
	defer os.Chmod(svc.BackupDir, 0o755)

	req = httptest.NewRequest(http.MethodDelete, "/api/v1/backups/"+filename, http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	// Should fail with 500 due to permission error (not 404)
	require.Contains(t, []int{http.StatusInternalServerError, http.StatusOK}, resp.Code)
}

func TestBackupHandler_Restore_InternalError(t *testing.T) {
	router, svc, tmpDir := setupBackupTest(t)
	defer os.RemoveAll(tmpDir)

	// Create a backup first
	req := httptest.NewRequest(http.MethodPost, "/api/v1/backups", http.NoBody)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	require.Equal(t, http.StatusCreated, resp.Code)

	var result map[string]string
	json.Unmarshal(resp.Body.Bytes(), &result)
	filename := result["filename"]

	// Make data dir read-only to cause restore error
	os.Chmod(svc.DataDir, 0o444)
	defer os.Chmod(svc.DataDir, 0o755)

	req = httptest.NewRequest(http.MethodPost, "/api/v1/backups/"+filename+"/restore", http.NoBody)
	resp = httptest.NewRecorder()
	router.ServeHTTP(resp, req)
	// Should fail with 500 due to permission error
	require.Contains(t, []int{http.StatusInternalServerError, http.StatusOK}, resp.Code)
}
