package projects

import (
	"bytes"
	"encoding/json"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"
	"senlinai-agent/backend/internal/config"
	"senlinai-agent/backend/internal/httpx"
	"senlinai-agent/backend/internal/logic/files"
	"senlinai-agent/backend/internal/models"
)

func TestCreateProjectHandlerPersistsMetadata(t *testing.T) {
	router, _, _ := newProjectsHandlerTestRouter(t)
	body, err := json.Marshal(gin.H{
		"name":        "Explore",
		"identifier":  "EXP",
		"icon":        "compass",
		"background":  "#165DFF",
		"description": "RSS exploration workspace",
	})
	require.NoError(t, err)
	req := httptest.NewRequest(http.MethodPost, "/api/projects", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer test-token")
	rec := httptest.NewRecorder()

	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusCreated, rec.Code)
	var project models.SenlinAgentProject
	require.NoError(t, models.DBService.Where("owner_id = ? AND identifier = ?", 1, "EXP").First(&project).Error)
	require.Equal(t, "Explore", project.Name)
	require.Equal(t, "compass", project.Icon)
	require.Equal(t, "#165DFF", project.Background)
	require.Equal(t, "RSS exploration workspace", project.Description)
}

func TestCreateTaskHandlerPersistsTask(t *testing.T) {
	router, project, _ := newProjectsHandlerTestRouter(t)
	body, err := json.Marshal(gin.H{"title": "整理需求", "description": "形成任务清单", "dueAt": "2026-07-21T09:30:00Z", "tag": "需求"})
	require.NoError(t, err)
	req := httptest.NewRequest(http.MethodPost, "/api/projects/1/tasks", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer test-token")
	rec := httptest.NewRecorder()

	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusCreated, rec.Code)
	var task models.SenlinAgentTask
	require.NoError(t, models.DBService.Where("project_id = ? AND title = ?", project.ID, "整理需求").First(&task).Error)
	require.Equal(t, "open", task.Status)
	require.NotNil(t, task.DueAt)
	require.NotNil(t, task.TagID)
}

func TestUploadSourceHandlerStoresFileAndSource(t *testing.T) {
	router, project, storageDir := newProjectsHandlerTestRouter(t)
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	require.NoError(t, writer.WriteField("title", "客户资料.txt"))
	fileWriter, err := writer.CreateFormFile("file", "客户资料.txt")
	require.NoError(t, err)
	_, err = fileWriter.Write([]byte("hello"))
	require.NoError(t, err)
	require.NoError(t, writer.Close())
	req := httptest.NewRequest(http.MethodPost, "/api/projects/1/sources", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	req.Header.Set("Authorization", "Bearer test-token")
	rec := httptest.NewRecorder()

	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusCreated, rec.Code)
	var source models.SenlinAgentSource
	require.NoError(t, models.DBService.Where("project_id = ? AND kind = ?", project.ID, "file").First(&source).Error)
	require.Equal(t, "客户资料.txt", source.Title)
	require.NotEmpty(t, source.FilePath)
	require.FileExists(t, filepath.Join(storageDir, source.FilePath))
}

func TestCreateCronPlanHandlerPersistsPlan(t *testing.T) {
	router, project, _ := newProjectsHandlerTestRouter(t)
	body, err := json.Marshal(gin.H{"title": "每日整理", "schedule": "0 9 * * *", "enabled": true, "nextRunAt": "2026-07-22T08:00:00Z"})
	require.NoError(t, err)
	req := httptest.NewRequest(http.MethodPost, "/api/projects/1/cron-plans", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer test-token")
	rec := httptest.NewRecorder()

	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusCreated, rec.Code)
	var plan models.SenlinAgentCronPlan
	require.NoError(t, models.DBService.Where("project_id = ? AND title = ?", project.ID, "每日整理").First(&plan).Error)
	require.Equal(t, "0 9 * * *", plan.Schedule)
	require.True(t, plan.Enabled)
	require.NotNil(t, plan.NextRunAt)
}

func TestCreateTagHandlerPersistsProjectTag(t *testing.T) {
	router, project, _ := newProjectsHandlerTestRouter(t)
	body, err := json.Marshal(gin.H{"name": "Design"})
	require.NoError(t, err)
	req := httptest.NewRequest(http.MethodPost, "/api/projects/1/tags", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer test-token")
	rec := httptest.NewRecorder()

	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusCreated, rec.Code)
	var tag models.SenlinAgentTag
	require.NoError(t, models.DBService.Where("project_id = ? AND name = ?", project.ID, "Design").First(&tag).Error)
	require.Equal(t, project.ID, tag.ProjectID)
}

func TestListTagsHandlerReturnsProjectTags(t *testing.T) {
	router, project, _ := newProjectsHandlerTestRouter(t)
	require.NoError(t, models.DBService.Create(&models.SenlinAgentTag{ProjectID: project.ID, Name: "Design"}).Error)
	req := httptest.NewRequest(http.MethodGet, "/api/projects/1/tags", nil)
	req.Header.Set("Authorization", "Bearer test-token")
	rec := httptest.NewRecorder()

	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code)
	var tags []models.SenlinAgentTag
	require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tags))
	require.Len(t, tags, 1)
	require.Equal(t, "Design", tags[0].Name)
}

func TestUpdateTaskHandlerPersistsTagAndStatus(t *testing.T) {
	router, project, _ := newProjectsHandlerTestRouter(t)
	task := models.SenlinAgentTask{ProjectID: project.ID, CreatedBy: 1, Title: "Draft", Status: "open"}
	require.NoError(t, models.DBService.Create(&task).Error)
	body, err := json.Marshal(gin.H{"title": "Draft v2", "description": "Updated", "completed": true, "tag": "Important"})
	require.NoError(t, err)
	req := httptest.NewRequest(http.MethodPatch, "/api/projects/1/tasks/1", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer test-token")
	rec := httptest.NewRecorder()

	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code)
	var updated models.SenlinAgentTask
	require.NoError(t, models.DBService.First(&updated, task.ID).Error)
	require.Equal(t, "Draft v2", updated.Title)
	require.Equal(t, "done", updated.Status)
	require.NotNil(t, updated.TagID)
}

func newProjectsHandlerTestRouter(t *testing.T) (*gin.Engine, *models.SenlinAgentProject, string) {
	t.Helper()
	newTestDB(t)
	require.NoError(t, models.DBService.Create(&models.SenlinAgentUser{Email: "david@example.com", DisplayName: "David", PasswordHash: "hash"}).Error)
	service := NewService()
	project, err := service.CreateProject(1, "Alpha", "")
	require.NoError(t, err)
	storageDir := t.TempDir()
	router := httpx.NewProtectedRouter(
		config.Config{Env: "test"},
		func(token string) (uint, error) { return 1, nil },
		NewHandler(service, files.NewService(storageDir)),
	)
	return router, project, storageDir
}
