package agent

import (
	"bytes"
	"context"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"net/textproto"
	"testing"

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

	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/attachment"
)

var testPNG = []byte{
	0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
	0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
	0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
	0x08, 0x06, 0x00, 0x00, 0x00, 0x1f, 0x15, 0xc4,
	0x89, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41,
	0x54, 0x78, 0x9c, 0x63, 0x00, 0x01, 0x00, 0x00,
	0x05, 0x00, 0x01, 0x0d, 0x0a, 0x2d, 0xb4, 0x00,
	0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
	0x42, 0x60, 0x82,
}

type fakeFileStore struct {
	memberID    string
	workspaceID string
	scopeID     string
	upload      attachment.CreateAttachmentParams
	avatarID    string
	avatarURL   string
}

func (fake *fakeFileStore) UploadAttachmentsForMember(_ context.Context, memberID string, workspaceID string, scopeID string, params []attachment.CreateAttachmentParams) ([]attachment.AppAttachment, error) {
	fake.memberID = memberID
	fake.workspaceID = workspaceID
	fake.scopeID = scopeID
	if len(params) == 1 {
		fake.upload = params[0]
	}
	return []attachment.AppAttachment{{ID: "att_1", Filename: fake.upload.Filename}}, nil
}

func (fake *fakeFileStore) UploadAgentAvatar(_ context.Context, _ string, _ string, upload attachment.CreateAttachmentParams) (string, error) {
	fake.upload = upload
	return fake.avatarID, nil
}

func (fake *fakeFileStore) UpdateAgentAvatarURL(_ context.Context, _ string, _ string, avatarURL string) error {
	fake.avatarURL = avatarURL
	return nil
}

func newMultipartUploadRequest(t *testing.T, target string, fields map[string]string, fieldName string, filename string, content []byte) *http.Request {
	t.Helper()
	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	for key, value := range fields {
		require.NoError(t, writer.WriteField(key, value))
	}
	part, err := writer.CreateFormFile(fieldName, filename)
	require.NoError(t, err)
	_, err = part.Write(content)
	require.NoError(t, err)
	require.NoError(t, writer.Close())

	request := httptest.NewRequest(http.MethodPost, target, &body)
	request.Header.Set("Authorization", "Bearer test-token")
	request.Header.Set("Content-Type", writer.FormDataContentType())
	return request
}

func TestFileHandlerUploadUsesNarrowInterfaces(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "0000000agent"}
	fileStore := &fakeFileStore{}
	handler := &FileHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		files: fileStore,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/upload", handler.Upload)

	request := newMultipartUploadRequest(t, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/upload", map[string]string{"scopeId": "chn_1"}, "file", "pixel.png", testPNG)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "0000000agent", fileStore.memberID)
	require.Equal(t, "wsp_test", fileStore.workspaceID)
	require.Equal(t, "chn_1", fileStore.scopeID)
	require.Equal(t, "pixel.png", fileStore.upload.Filename)
	require.Equal(t, "image/png", fileStore.upload.ContentType)
	require.Equal(t, testPNG, fileStore.upload.Blob)
	require.JSONEq(t, `{"id":"att_1","filename":"pixel.png","contentType":"image/png","sizeBytes":67}`, response.Body.String())
}

func TestFileHandlerUploadRejectsLegacyTargetField(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "0000000agent"}
	fileStore := &fakeFileStore{}
	handler := &FileHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		files: fileStore,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/upload", handler.Upload)

	request := newMultipartUploadRequest(t, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/upload", map[string]string{"space" + "Id": "chn_legacy"}, "file", "pixel.png", testPNG)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Contains(t, response.Body.String(), "scopeId is required")
	require.Empty(t, fileStore.scopeID)
}

func TestFileHandlerUploadAcceptsGenericAttachment(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "0000000agent"}
	fileStore := &fakeFileStore{}
	handler := &FileHandler{
		auth:  fakeAgentAuthenticator{agent: agentIdentity},
		files: fileStore,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/upload", handler.Upload)

	payload := []byte("name,email\nAda,ada@example.com\n")
	request := newMultipartUploadRequest(t, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/upload", map[string]string{"scopeId": "chn_1"}, "file", "registrations.csv", payload)
	response := httptest.NewRecorder()

	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "registrations.csv", fileStore.upload.Filename)
	require.Equal(t, "text/csv; charset=utf-8", fileStore.upload.ContentType)
	require.Equal(t, payload, fileStore.upload.Blob)
	require.JSONEq(t, `{"id":"att_1","filename":"registrations.csv","contentType":"text/csv; charset=utf-8","sizeBytes":31}`, response.Body.String())
}

func TestFileHandlerAvatarPersistsVersionedAgentURL(t *testing.T) {
	gin.SetMode(gin.TestMode)

	agentIdentity := agent.AuthenticatedAgent{AgentID: "agt_0000000000test", WorkspaceID: "wsp_test", MemberID: "0000000agent"}
	store := &fakeFileStore{avatarID: "avt_1"}
	handler := &FileHandler{
		auth:   fakeAgentAuthenticator{agent: agentIdentity},
		files:  store,
		agents: store,
	}

	router := gin.New()
	router.POST("/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/avatar", handler.Avatar)

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	part, err := writer.CreatePart(textproto.MIMEHeader{
		"Content-Disposition": []string{`form-data; name="file"; filename="avatar.png"`},
		"Content-Type":        []string{"image/png"},
	})
	require.NoError(t, err)
	_, err = part.Write(testPNG)
	require.NoError(t, err)
	require.NoError(t, writer.Close())
	request := httptest.NewRequest(http.MethodPost, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/runtime/avatar", &body)
	request.Header.Set("Authorization", "Bearer test-token")
	request.Header.Set("Content-Type", writer.FormDataContentType())
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)

	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "/api/v1/workspaces/wsp_test/agents/agt_0000000000test/avatar?v=avt_1", store.avatarURL)
	require.JSONEq(t, `{
		"id":"avt_1",
		"avatarUrl":"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/avatar?v=avt_1",
		"url":"/api/v1/workspaces/wsp_test/agents/agt_0000000000test/avatar?v=avt_1"
	}`, response.Body.String())
}
