package dialectsfcrm

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

	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/dialect"
	"github.com/nexuscrm/shared/pkg/models"
)

type fileContractStub struct {
	uploadReq   contract.FileUploadRequest
	detachedReq contract.FileUploadRequest
	detachedRef contract.FileBlobReference
	download    *contract.FileDownload
	found       bool
}

func (s *fileContractStub) StoreDetachedFile(_ context.Context, req contract.FileUploadRequest) (*contract.FileUploadResult, error) {
	s.detachedReq = req
	return &contract.FileUploadResult{ID: req.ID, Name: "contract.pdf", Size: 7, Mime: "application/pdf"}, nil
}

func (s *fileContractStub) StoreRecordAttachment(_ context.Context, req contract.FileUploadRequest) (*contract.FileUploadResult, error) {
	s.uploadReq = req
	return &contract.FileUploadResult{ID: "file-1", Name: "contract.pdf", Size: 7, Mime: "application/pdf"}, nil
}

func (s *fileContractStub) OpenDetachedFile(_ context.Context, ref contract.FileBlobReference, _ *models.UserSession) (*contract.FileDownload, error) {
	s.detachedRef = ref
	return s.download, nil
}

func (s *fileContractStub) OpenRecordAttachment(context.Context, string, *models.UserSession) (*contract.FileDownload, bool, error) {
	if !s.found && s.download != nil {
		return s.download, true, nil
	}
	return s.download, s.found, nil
}

func (s *fileContractStub) AuthorizeRecordAttachmentMetadata(context.Context, string, *models.UserSession) (*contract.RecordAttachmentMetadata, bool, error) {
	return nil, false, nil
}

func (s *fileContractStub) ResolveAuthorizedRecordAttachmentSource(context.Context, contract.RecordAttachmentSourceRequest, contract.RecordAttachmentSourceLookup, *models.UserSession) (*contract.RecordAttachmentSourceResult, bool, error) {
	return nil, false, nil
}

func (s *fileContractStub) DeleteRecordAttachment(context.Context, string, *models.UserSession) error {
	return nil
}

func TestUploadRecordFileNeutralHandlerProjectsV63RecordFileURL(t *testing.T) {
	body, contentType := multipartBody(t, map[string]string{
		"file":                         "contract.pdf",
		uploadFieldParentID:            "001000000000001",
		uploadFieldParentObjectAPIName: "Account",
	}, []byte("%PDF-1.4"))
	files := &fileContractStub{}

	resp, err := UploadRecordFileNeutralHandler(context.Background(), &dialect.Request{
		Version:    "v63.0",
		Header:     map[string][]string{"Content-Type": []string{contentType}},
		BodyStream: bytes.NewReader(body),
		User:       &models.UserSession{ID: "005000000000001"},
	}, dialect.Deps{Contracts: contract.Deps{Files: files}})
	if err != nil {
		t.Fatalf("UploadRecordFileNeutralHandler err = %v", err)
	}
	if resp.Status != http.StatusCreated {
		t.Fatalf("status = %d, want 201; body=%#v", resp.Status, resp.Body)
	}
	got := resp.Body.(map[string]any)["data"].(map[string]any)
	want := map[string]any{
		"Id":          "file-1",
		"Name":        "contract.pdf",
		"Size":        int64(7),
		"Mime":        "application/pdf",
		"DownloadUrl": "/services/data/v63.0/files/file-1/download",
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("body data = %#v, want %#v", got, want)
	}
	if files.uploadReq.ParentID != "001000000000001" || files.uploadReq.ParentObjectAPIName != "Account" {
		t.Fatalf("upload parent = %q/%q", files.uploadReq.ParentObjectAPIName, files.uploadReq.ParentID)
	}
}

func TestUploadLegacyContentVersionOwnsDetachedBlobReference(t *testing.T) {
	body, contentType := multipartBody(t, map[string]string{
		"file": "contract.pdf",
	}, []byte("%PDF-1.4"))
	files := &fileContractStub{}

	resp, err := UploadLegacyContentVersionNeutralHandler(context.Background(), &dialect.Request{
		Version:    "v63.0",
		Header:     map[string][]string{"Content-Type": []string{contentType}},
		BodyStream: bytes.NewReader(body),
		User:       &models.UserSession{ID: "005000000000001"},
	}, dialect.Deps{Contracts: contract.Deps{Files: files}})
	if err != nil {
		t.Fatalf("UploadLegacyContentVersionNeutralHandler err = %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%#v", resp.Status, resp.Body)
	}
	req := files.detachedReq
	if req.ID == "" || !isLegacyContentVersionID(req.ID) {
		t.Fatalf("detached ID = %q, want generated ContentVersion-compatible ID", req.ID)
	}
	if req.StorageKey != legacyContentVersionStorageKey(req.ID) {
		t.Fatalf("StorageKey = %q, want %q", req.StorageKey, legacyContentVersionStorageKey(req.ID))
	}
	if req.LegacyFileName != req.ID {
		t.Fatalf("LegacyFileName = %q, want ID %q", req.LegacyFileName, req.ID)
	}
	got := resp.Body.(map[string]any)["data"].(map[string]any)
	if got["DownloadUrl"] != legacyContentVersionDownloadURL("v63.0", req.ID) {
		t.Fatalf("DownloadUrl = %v, want ContentVersion URL for %s", got["DownloadUrl"], req.ID)
	}
}

func TestDownloadRecordFileNeutralHandlerReturnsRawStream(t *testing.T) {
	files := &fileContractStub{download: &contract.FileDownload{
		Reader:      io.NopCloser(bytes.NewReader([]byte("raw"))),
		FileName:    "report.txt",
		Size:        3,
		ContentType: "text/plain",
	}}
	resp, err := DownloadRecordFileNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"id": "file-1"},
	}, dialect.Deps{Contracts: contract.Deps{Files: files}})
	if err != nil {
		t.Fatalf("DownloadRecordFileNeutralHandler err = %v", err)
	}
	if resp.Status != http.StatusOK || resp.Stream == nil || resp.Stream.ContentLength == nil || *resp.Stream.ContentLength != 3 {
		t.Fatalf("response = %#v", resp)
	}
	if got := resp.Headers["Content-Disposition"]; got != `attachment; filename="report.txt"` {
		t.Fatalf("Content-Disposition = %q", got)
	}
}

func TestDownloadRecordFileNeutralHandlerBlocksExecutableMIME(t *testing.T) {
	files := &fileContractStub{download: &contract.FileDownload{
		Reader:      io.NopCloser(bytes.NewReader([]byte("<svg></svg>"))),
		FileName:    "diagram.svg",
		Size:        11,
		ContentType: "image/svg+xml; charset=utf-8",
	}}
	resp, err := DownloadRecordFileNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"id": "file-1"},
	}, dialect.Deps{Contracts: contract.Deps{Files: files}})
	if err != nil {
		t.Fatalf("DownloadRecordFileNeutralHandler err = %v", err)
	}
	if resp.Stream == nil {
		t.Fatalf("Stream is nil: %#v", resp)
	}
	if resp.Stream.ContentType != "application/octet-stream" {
		t.Fatalf("ContentType = %q, want application/octet-stream", resp.Stream.ContentType)
	}
}

func TestDownloadLegacyContentVersionTriesRecordAttachmentBeforeUUIDValidation(t *testing.T) {
	files := &fileContractStub{download: &contract.FileDownload{
		Reader:      io.NopCloser(bytes.NewReader([]byte("raw"))),
		FileName:    "report.txt",
		Size:        3,
		ContentType: "text/plain",
	}}
	resp, err := DownloadLegacyContentVersionNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"fileId": "file-1"},
	}, dialect.Deps{Contracts: contract.Deps{Files: files}})
	if err != nil {
		t.Fatalf("DownloadLegacyContentVersionNeutralHandler err = %v", err)
	}
	if resp.Status != http.StatusOK || resp.Stream == nil {
		t.Fatalf("response = %#v, want 200 stream", resp)
	}
}

func TestDownloadLegacyContentVersionRejectsNonUUIDStem(t *testing.T) {
	resp, err := DownloadLegacyContentVersionNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		Params:  map[string]string{"fileId": "not-a-uuid.pdf"},
	}, dialect.Deps{Contracts: contract.Deps{Files: &fileContractStub{}}})
	if err != nil {
		t.Fatalf("DownloadLegacyContentVersionNeutralHandler err = %v", err)
	}
	if resp.Status != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400; body=%#v", resp.Status, resp.Body)
	}
}

func TestResolveContentVersionBlobReferenceOwnsLegacyURLParsing(t *testing.T) {
	fileID := "550e8400-e29b-41d4-a716-446655440000.pdf"
	ref, ok := ResolveContentVersionBlobReference("/services/data/v63.0/sobjects/ContentVersion/" + fileID + "/VersionData")
	if !ok {
		t.Fatal("ResolveContentVersionBlobReference returned ok=false")
	}
	if ref.StorageKey != legacyContentVersionStorageKey(fileID) || ref.LegacyFileName != fileID || ref.FileName != fileID {
		t.Fatalf("ref = %#v, want ContentVersion blob reference for %s", ref, fileID)
	}
	if _, ok := ResolveContentVersionBlobReference("/services/data/v63.0/files/file-1/download"); ok {
		t.Fatal("ResolveContentVersionBlobReference accepted non-ContentVersion URL")
	}
}

func multipartBody(t *testing.T, fields map[string]string, content []byte) ([]byte, string) {
	t.Helper()
	var buf bytes.Buffer
	writer := multipart.NewWriter(&buf)
	for key, value := range fields {
		if key == "file" {
			part, err := writer.CreateFormFile(key, value)
			if err != nil {
				t.Fatalf("CreateFormFile: %v", err)
			}
			if _, err := part.Write(content); err != nil {
				t.Fatalf("write file: %v", err)
			}
			continue
		}
		if err := writer.WriteField(key, value); err != nil {
			t.Fatalf("WriteField: %v", err)
		}
	}
	if err := writer.Close(); err != nil {
		t.Fatalf("Close multipart: %v", err)
	}
	return buf.Bytes(), writer.FormDataContentType()
}
