package feedback

import (
	"archive/zip"
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha256"
	"encoding/hex"
	"io"
	"mime/multipart"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"ymhut-box/server/unified-management/internal/config"
	"ymhut-box/server/unified-management/internal/db"
)

func TestSignedMultipartSubmission(t *testing.T) {
	root := t.TempDir()
	cfg := testConfig(root)
	store, err := db.Open(cfg)
	if err != nil {
		t.Fatal(err)
	}
	defer store.Close()
	service := NewService(cfg, store)
	plain := zipBytes(t, map[string]string{
		"feedback.json": `{"request":{"title":"Crash on launch","type":"issue","severity":"major","contact":"user@example.com","body":"It crashes."}}`,
		"summary.txt":   "launch failure",
	})
	encrypted := encryptPackageForTest(t, plain, cfg.PackageEncryptionKey)
	encryptedHash := sha256HexTest(encrypted)
	plainHash := sha256HexTest(plain)
	timestamp := itoa(int(time.Now().Unix()))
	payload := `{"feedbackCode":"FB-20260625-ABCDEF","title":"Crash on launch","type":"issue","severity":"major","contact":"user@example.com","bodyLength":11,"packageEncrypted":true,"encryption":"YMHUTFB1","packageBytes":` + itoa(len(encrypted)) + `,"packageSha256":"` + encryptedHash + `","plainPackageBytes":` + itoa(len(plain)) + `,"plainPackageSha256":"` + plainHash + `","createdAt":"2026-06-25T00:00:00Z"}`
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	_ = writer.WriteField("payload", payload)
	_ = writer.WriteField("timestamp", timestamp)
	_ = writer.WriteField("nonce", "abc123")
	_ = writer.WriteField("packageSha256", encryptedHash)
	_ = writer.WriteField("signature", SignWithKey(cfg.ClientSignatureKey, timestamp, "abc123", encryptedHash, payload))
	part, err := writer.CreateFormFile("package", "feedback.ymfb")
	if err != nil {
		t.Fatal(err)
	}
	_, _ = io.Copy(part, bytes.NewReader(encrypted))
	_ = writer.Close()
	req := httptest.NewRequest("POST", "/", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	item, err := service.Submit(req)
	if err != nil {
		t.Fatal(err)
	}
	if item.Code != "FB-20260625-ABCDEF" || !strings.Contains(item.IncludedFiles, "feedback.json") {
		t.Fatalf("unexpected item: %#v", item)
	}
	if _, err := store.GetFeedback(item.Code); err != nil {
		t.Fatal(err)
	}
}

func TestZipPathEscapeRejected(t *testing.T) {
	root := t.TempDir()
	cfg := testConfig(root)
	store, err := db.Open(cfg)
	if err != nil {
		t.Fatal(err)
	}
	defer store.Close()
	plain := zipBytes(t, map[string]string{"../escape.txt": "bad", "feedback.json": `{}`})
	if _, err := ReadFeedbackPackageWithGuard(plain, cfg.UploadGuard); err == nil {
		t.Fatal("expected unsafe zip entry error")
	}
}

func testConfig(root string) *config.Config {
	return &config.Config{
		StorageDir:             filepath.Join(root, "storage"),
		ClientSignatureKey:     "ymhut-box-feedback-client-v1",
		PackageEncryptionKey:   "ymhut-box-feedback-package-v1",
		TimestampWindowSeconds: 600,
		MaxRequestBytes:        12 << 20,
		MaxPackageBytes:        10 << 20,
		Database: config.DatabaseConfig{
			Provider:               "sqlite",
			SQLitePath:             filepath.Join(root, "storage", "unified.sqlite"),
			FailoverEnabled:        true,
			HealthIntervalSec:      3600,
			MaxOpenConns:           1,
			MaxIdleConns:           1,
			ConnMaxLifetimeSeconds: 60,
		},
		UploadGuard: config.UploadGuardConfig{MaxZipFiles: 80, MaxDecompressedBytes: 30 << 20, MaxSingleFileBytes: 8 << 20, MaxCompressionRatio: 120, MaxReadableTextBytes: 256 << 10, AllowUnexpectedZipFiles: true},
	}
}

func zipBytes(t *testing.T, files map[string]string) []byte {
	t.Helper()
	var buf bytes.Buffer
	writer := zip.NewWriter(&buf)
	for name, body := range files {
		entry, err := writer.Create(name)
		if err != nil {
			t.Fatal(err)
		}
		_, _ = entry.Write([]byte(body))
	}
	if err := writer.Close(); err != nil {
		t.Fatal(err)
	}
	return buf.Bytes()
}

func encryptPackageForTest(t *testing.T, plain []byte, keyMaterial string) []byte {
	t.Helper()
	key := sha256.Sum256([]byte(keyMaterial))
	block, err := aes.NewCipher(key[:])
	if err != nil {
		t.Fatal(err)
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		t.Fatal(err)
	}
	nonce := []byte("123456789012")
	sealed := gcm.Seal(nil, nonce, plain, []byte(PackageMagic))
	ciphertext := sealed[:len(sealed)-gcm.Overhead()]
	tag := sealed[len(sealed)-gcm.Overhead():]
	out := []byte(PackageMagic)
	out = append(out, nonce...)
	out = append(out, tag...)
	out = append(out, ciphertext...)
	return out
}

func sha256HexTest(data []byte) string {
	sum := sha256.Sum256(data)
	return hex.EncodeToString(sum[:])
}

func itoa(value int) string {
	if value == 0 {
		return "0"
	}
	var buf [20]byte
	i := len(buf)
	for value > 0 {
		i--
		buf[i] = byte('0' + value%10)
		value /= 10
	}
	return string(buf[i:])
}
