package kb

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

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/meshxapi"
	"github.com/tidbcloud/AgentTeam/internal/identity/trusted"
	kbservice "github.com/tidbcloud/AgentTeam/internal/service/kb"
)

type fakeDashboardAPIService struct {
	status       kbservice.UADashboardStatus
	file         kbservice.UADashboardFile
	importPath   string
	importName   string
	readCalls    int
	preflightErr error
	importCalls  int
}

func (f *fakeDashboardAPIService) AuthorizeUADashboardImport(context.Context, string, string, string, string) (kbservice.UADashboardImportLimits, error) {
	return kbservice.DefaultUADashboardImportLimits(), f.preflightErr
}

func (f *fakeDashboardAPIService) GetUADashboardStatus(context.Context, string, string, string, string) (kbservice.UADashboardStatus, error) {
	return f.status, nil
}
func (f *fakeDashboardAPIService) GetUADashboardSourcePreview(context.Context, string, string, string, string) (kbservice.UADashboardSourcePreview, error) {
	return kbservice.UADashboardSourcePreview{RepoOwner: "org", RepoName: "repo", SourceCommitSHA: "sha"}, nil
}
func (f *fakeDashboardAPIService) ImportUADashboard(_ context.Context, _, _, _, _ string, input kbservice.UADashboardImport) (kbservice.UADashboardStatus, error) {
	f.importPath, f.importName = input.ArchivePath, input.Filename
	f.importCalls++
	_, err := os.Stat(input.ArchivePath)
	return f.status, err
}

type countingBody struct{ reads int }

func (b *countingBody) Read([]byte) (int, error) { b.reads++; return 0, io.EOF }
func (*countingBody) Close() error               { return nil }

func TestDashboardImportRejectsPreflightBeforeReadingBody(t *testing.T) {
	fake := &fakeDashboardAPIService{preflightErr: kbservice.ErrForbidden}
	handler := &DashboardHandler{service: fake}
	body := &countingBody{}
	req := httptest.NewRequest(http.MethodPost, "/", nil)
	req.Body = body
	req = req.WithContext(trusted.WithIdentity(req.Context(), trusted.Identity{Subject: "viewer"}))
	response := httptest.NewRecorder()
	handler.importHTTP().ServeHTTP(response, req)
	require.Equal(t, http.StatusForbidden, response.Code)
	require.Zero(t, body.reads)
	require.Zero(t, fake.importCalls)
}
func (f *fakeDashboardAPIService) ReadUADashboardFile(context.Context, string, string, string, string, string) (kbservice.UADashboardFile, error) {
	f.readCalls++
	return f.file, nil
}

type dashboardOnlyService struct{ *fakeDashboardAPIService }

func TestDashboardStatusExposesRetainedPublicationVersion(t *testing.T) {
	handler := &DashboardHandler{service: &fakeDashboardAPIService{status: kbservice.UADashboardStatus{
		State: "ready", SourceID: "src-1", SourceCommitSHA: "source-current",
		DashboardSourceCommitSHA: "source-uploaded", DashboardCommitSHA: "dashboard",
		UpdateAvailable: true, AcceptedFormats: []string{"zip"}, Limits: kbservice.DefaultUADashboardImportLimits(),
	}}}
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	req.Header.Set(meshxapi.HeaderWorkspaceID, "ws-1")
	req.SetPathValue("kb_id", "kb-1")
	req.SetPathValue("source_id", "src-1")
	response, err := handler.Status(meshxapi.AuthenticatedRequest{Request: req, Identity: trusted.Identity{Subject: "viewer"}})
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.Status)
	body := response.Body.(dto.UADashboardStatusResponse)
	require.Equal(t, "source-uploaded", body.DashboardSourceCommitSHA)
	require.True(t, body.UpdateAvailable)
}

func TestDashboardImportAcceptsOneFileAndCleansTemp(t *testing.T) {
	fake := &fakeDashboardAPIService{status: kbservice.UADashboardStatus{State: "ready", SourceID: "src-1", AcceptedFormats: []string{"zip"}, Limits: kbservice.DefaultUADashboardImportLimits()}}
	handler := &DashboardHandler{service: fake}
	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	part, err := writer.CreateFormFile("file", "dashboard.zip")
	require.NoError(t, err)
	_, err = part.Write([]byte("PK\x03\x04"))
	require.NoError(t, err)
	require.NoError(t, writer.Close())
	req := httptest.NewRequest(http.MethodPost, "/api/v1/kb/kb-1/sources/src-1/dashboard/import", &body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	req.Header.Set("X-Workspace-Id", "ws-1")
	req.SetPathValue("kb_id", "kb-1")
	req.SetPathValue("source_id", "src-1")
	req = req.WithContext(trusted.WithIdentity(req.Context(), trusted.Identity{Subject: "owner"}))
	response := httptest.NewRecorder()
	handler.importHTTP().ServeHTTP(response, req)
	require.Equal(t, http.StatusOK, response.Code)
	require.Equal(t, "dashboard.zip", fake.importName)
	_, err = os.Stat(fake.importPath)
	require.ErrorIs(t, err, os.ErrNotExist)
}

func TestDashboardImportRejectsExtraMultipartField(t *testing.T) {
	fake := &fakeDashboardAPIService{}
	handler := &DashboardHandler{service: fake}
	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	part, err := writer.CreateFormFile("file", "dashboard.zip")
	require.NoError(t, err)
	_, _ = part.Write([]byte("PK\x03\x04"))
	require.NoError(t, writer.WriteField("url", "https://example.invalid/archive.zip"))
	require.NoError(t, writer.Close())
	req := httptest.NewRequest(http.MethodPost, "/", &body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	req = req.WithContext(trusted.WithIdentity(req.Context(), trusted.Identity{Subject: "owner"}))
	response := httptest.NewRecorder()
	handler.importHTTP().ServeHTTP(response, req)
	require.Equal(t, http.StatusBadRequest, response.Code)
	require.Empty(t, fake.importPath)
}

func TestDashboardFileChecksServiceBeforeETag(t *testing.T) {
	fake := &fakeDashboardAPIService{file: kbservice.UADashboardFile{Content: []byte(`{"graph":true}`), ETag: `"commit:blob"`}}
	handler := &DashboardHandler{service: fake}
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	req.Header.Set("If-None-Match", `"commit:blob"`)
	req = req.WithContext(trusted.WithIdentity(req.Context(), trusted.Identity{Subject: "viewer"}))
	response := httptest.NewRecorder()
	handler.fileHTTP().ServeHTTP(response, req)
	require.Equal(t, http.StatusNotModified, response.Code)
	require.Equal(t, 1, fake.readCalls)
	require.Equal(t, "application/json; charset=utf-8", response.Header().Get("Content-Type"))
	require.Equal(t, "private", response.Header().Get("Cache-Control"))
}

func TestDashboardSourcePreviewReturnsNarrowMapping(t *testing.T) {
	handler := &DashboardHandler{service: &fakeDashboardAPIService{}}
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	req.Header.Set(meshxapi.HeaderWorkspaceID, "ws-1")
	req.SetPathValue("kb_id", "kb-1")
	req.SetPathValue("source_id", "src-1")
	response, err := handler.SourcePreview(meshxapi.AuthenticatedRequest{Request: req, Identity: trusted.Identity{Subject: "viewer"}})
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, response.Status)
	require.Equal(t, dto.UADashboardSourcePreviewResponse{RepoOwner: "org", RepoName: "repo", SourceCommitSHA: "sha"}, response.Body)
}
