package services

import (
	"bytes"
	"database/sql"
	"errors"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"testing"

	"quyun/v2/app/commands/testx"
	"quyun/v2/app/errorx"
	common_dto "quyun/v2/app/http/v1/dto"

	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/suite"
	"go.ipao.vip/atom/contracts"
	"go.uber.org/dig"
)

type CommonTestSuiteInjectParams struct {
	dig.In

	DB       *sql.DB
	Initials []contracts.Initial `group:"initials"`
}

type CommonTestSuite struct {
	suite.Suite
	CommonTestSuiteInjectParams
}

func Test_Common(t *testing.T) {
	providers := testx.Default().With(Provide)

	testx.Serve(providers, t, func(p CommonTestSuiteInjectParams) {
		suite.Run(t, &CommonTestSuite{CommonTestSuiteInjectParams: p})
	})
}

func (s *CommonTestSuite) Test_AbortUpload() {
	Convey("AbortUpload", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(1)
		ownerID := int64(1001)
		tempDir := s.T().TempDir()
		So(Common.storage, ShouldNotBeNil)
		Common.storage.Config.LocalPath = tempDir

		newUpload := func() string {
			form := &common_dto.UploadInitForm{
				Filename: "sample.mp4",
				Type:     "video",
				MimeType: "video/mp4",
			}
			resp, err := Common.InitUpload(ctx, tenantID, ownerID, form)
			So(err, ShouldBeNil)
			So(resp.UploadID, ShouldNotBeBlank)
			return resp.UploadID
		}

		Convey("should allow owner to abort", func() {
			uploadID := newUpload()

			err := Common.AbortUpload(ctx, tenantID, ownerID, uploadID)
			So(err, ShouldBeNil)
		})

		Convey("should reject abort by other user", func() {
			uploadID := newUpload()
			otherID := int64(2002)

			err := Common.AbortUpload(ctx, tenantID, otherID, uploadID)
			So(err, ShouldNotBeNil)

			var appErr *errorx.AppError
			So(errors.As(err, &appErr), ShouldBeTrue)
			So(appErr.Code, ShouldEqual, errorx.ErrForbidden.Code)

			So(Common.AbortUpload(ctx, tenantID, ownerID, uploadID), ShouldBeNil)
		})

		Convey("should return not found for missing upload", func() {
			err := Common.AbortUpload(ctx, tenantID, ownerID, "missing-upload")
			So(err, ShouldNotBeNil)

			var appErr *errorx.AppError
			So(errors.As(err, &appErr), ShouldBeTrue)
			So(appErr.Code, ShouldEqual, errorx.ErrRecordNotFound.Code)
		})
	})
}

func (s *CommonTestSuite) Test_UploadPart() {
	Convey("UploadPart", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(1)
		ownerID := int64(1001)
		otherID := int64(2002)
		tempDir := s.T().TempDir()
		So(Common.storage, ShouldNotBeNil)
		Common.storage.Config.LocalPath = tempDir

		newUpload := func() string {
			form := &common_dto.UploadInitForm{
				Filename: "sample.mp4",
				Type:     "video",
				MimeType: "video/mp4",
			}
			resp, err := Common.InitUpload(ctx, tenantID, ownerID, form)
			So(err, ShouldBeNil)
			So(resp.UploadID, ShouldNotBeBlank)
			return resp.UploadID
		}

		newFileHeader := func() *multipart.FileHeader {
			body := &bytes.Buffer{}
			writer := multipart.NewWriter(body)

			part, err := writer.CreateFormFile("file", "sample.txt")
			So(err, ShouldBeNil)
			_, err = part.Write([]byte("hello world"))
			So(err, ShouldBeNil)

			So(writer.Close(), ShouldBeNil)

			req := httptest.NewRequest(http.MethodPost, "/upload", body)
			req.Header.Set("Content-Type", writer.FormDataContentType())
			So(req.ParseMultipartForm(32<<20), ShouldBeNil)
			So(req.MultipartForm, ShouldNotBeNil)
			So(req.MultipartForm.File["file"], ShouldNotBeEmpty)
			return req.MultipartForm.File["file"][0]
		}

		Convey("should allow owner to upload part", func() {
			uploadID := newUpload()
			form := &common_dto.UploadPartForm{UploadID: uploadID, PartNumber: 1}
			err := Common.UploadPart(ctx, tenantID, ownerID, newFileHeader(), form)
			So(err, ShouldBeNil)
		})

		Convey("should reject other user", func() {
			uploadID := newUpload()
			form := &common_dto.UploadPartForm{UploadID: uploadID, PartNumber: 1}
			err := Common.UploadPart(ctx, tenantID, otherID, newFileHeader(), form)
			So(err, ShouldNotBeNil)

			var appErr *errorx.AppError
			So(errors.As(err, &appErr), ShouldBeTrue)
			So(appErr.Code, ShouldEqual, errorx.ErrForbidden.Code)
		})
	})
}
