package weed_server

import (
	"context"
	"testing"
	"time"

	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
	"github.com/seaweedfs/seaweedfs/weed/util"
)

func mustSetObjectTxnStorageConf(t *testing.T, fs *FilerServer, conf *filer_pb.FilerConf_PathConf) {
	t.Helper()

	if err := fs.filer.FilerConf.SetLocationConf(conf); err != nil {
		t.Fatalf("SetLocationConf(%q): %v", conf.LocationPrefix, err)
	}
}

func TestObjectTransactionPutAppliesConfiguredTTLAndExpires(t *testing.T) {
	fs, store := newTxnTestServer(t, nil)

	mustSetObjectTxnStorageConf(t, fs, &filer_pb.FilerConf_PathConf{
		LocationPrefix: "/buckets/video/",
		Ttl:            "1h",
	})

	old := time.Now().Add(-2 * time.Hour)

	resp, err := fs.ObjectTransaction(context.Background(), &filer_pb.ObjectTransactionRequest{
		LockKey: "/buckets/video/expired.mp4",
		Mutations: []*filer_pb.ObjectMutation{
			{
				Type:      filer_pb.ObjectMutation_PUT,
				Directory: "/buckets/video",
				Entry: &filer_pb.Entry{
					Name: "expired.mp4",
					Attributes: &filer_pb.FuseAttributes{
						Crtime:   time.Now().Unix(),
						Mtime:    old.Unix(),
						FileMode: 0644,
						FileSize: 123,
						TtlSec:   0,
					},
					Extended: map[string][]byte{
						"Seaweed-X-Amz-Owner": []byte("admin"),
						"Seaweed-X-Amz-ETag":  []byte("abc123"),
					},
				},
			},
		},
	})
	if err != nil {
		t.Fatalf("ObjectTransaction transport error: %v", err)
	}
	if resp.Error != "" {
		t.Fatalf("ObjectTransaction response error: %q", resp.Error)
	}

	entry := store.entries["/buckets/video/expired.mp4"]
	if entry == nil {
		t.Fatalf("PUT should create /buckets/video/expired.mp4")
	}
	if got := entry.TtlSec; got != 3600 {
		t.Fatalf("TtlSec = %d, want 3600 from fs.configure storage rule", got)
	}
	if _, found := entry.Extended["X-Seaweedfs-Expires-S3"]; !found {
		t.Fatalf("Header X-Seaweedfs-Expires-S3 not found")
	}

	_, err = fs.filer.FindEntry(context.Background(), util.FullPath("/buckets/video/expired.mp4"))
	if err != filer_pb.ErrNotFound {
		t.Fatalf("expired entry lookup error = %v, want %v", err, filer_pb.ErrNotFound)
	}

	if _, ok := store.entries["/buckets/video/expired.mp4"]; ok {
		t.Fatalf("expired entry should be deleted from metadata store")
	}
}

func TestObjectTransactionPutPreservesExplicitTTL(t *testing.T) {
	fs, store := newTxnTestServer(t, nil)

	mustSetObjectTxnStorageConf(t, fs, &filer_pb.FilerConf_PathConf{
		LocationPrefix: "/buckets/video/",
		Ttl:            "1h",
	})

	now := time.Unix(1700000000, 0)

	resp, err := fs.ObjectTransaction(context.Background(), &filer_pb.ObjectTransactionRequest{
		LockKey: "/buckets/video/lifecycle.mp4",
		Mutations: []*filer_pb.ObjectMutation{
			{
				Type:      filer_pb.ObjectMutation_PUT,
				Directory: "/buckets/video",
				Entry: &filer_pb.Entry{
					Name: "lifecycle.mp4",
					Attributes: &filer_pb.FuseAttributes{
						Crtime:   time.Now().Add(-2 * time.Hour).Unix(),
						Mtime:    now.Unix(),
						FileMode: 0644,
						FileSize: 123,
						TtlSec:   7200,
					},
					Extended: map[string][]byte{
						"Seaweed-X-Amz-Owner": []byte("admin"),
						"Seaweed-X-Amz-ETag":  []byte("abc123"),
					},
				},
			},
		},
	})
	if err != nil {
		t.Fatalf("ObjectTransaction transport error: %v", err)
	}
	if resp.Error != "" {
		t.Fatalf("ObjectTransaction response error: %q", resp.Error)
	}

	entry := store.entries["/buckets/video/lifecycle.mp4"]
	if entry == nil {
		t.Fatalf("PUT should create /buckets/video/lifecycle.mp4")
	}
	if got := entry.TtlSec; got != 7200 {
		t.Fatalf("TtlSec = %d, want explicit TTL 7200 to win over fs.configure", got)
	}
	if _, found := entry.Extended["X-Seaweedfs-Expires-S3"]; !found {
		t.Fatalf("Header X-Seaweedfs-Expires-S3 not found")
	}
}

func TestObjectTransactionPutClearsTTLForRemoteEntry(t *testing.T) {
	fs, store := newTxnTestServer(t, nil)

	mustSetObjectTxnStorageConf(t, fs, &filer_pb.FilerConf_PathConf{
		LocationPrefix: "/buckets/video/",
		Ttl:            "1h",
	})

	now := time.Unix(1700000000, 0)

	resp, err := fs.ObjectTransaction(context.Background(), &filer_pb.ObjectTransactionRequest{
		LockKey: "/buckets/video/remote",
		Mutations: []*filer_pb.ObjectMutation{
			{
				Type:      filer_pb.ObjectMutation_PUT,
				Directory: "/buckets/video",
				Entry: &filer_pb.Entry{
					Name: "remote",
					Attributes: &filer_pb.FuseAttributes{
						Crtime:   now.Unix(),
						Mtime:    now.Unix(),
						FileMode: 0644,
						FileSize: 123,
						TtlSec:   7200,
					},
					RemoteEntry: &filer_pb.RemoteEntry{RemoteSize: 123},
				},
			},
		},
	})
	if err != nil {
		t.Fatalf("ObjectTransaction transport error: %v", err)
	}
	if resp.Error != "" {
		t.Fatalf("ObjectTransaction response error: %q", resp.Error)
	}

	entry := store.entries["/buckets/video/remote"]
	if entry == nil {
		t.Fatalf("remote entry should be created")
	}
	if got := entry.TtlSec; got != 0 {
		t.Fatalf("remote entry TtlSec = %d, want 0", got)
	}
}

func TestObjectTransactionPutHonorsMaxFileNameLengthRule(t *testing.T) {
	fs, store := newTxnTestServer(t, nil)

	mustSetObjectTxnStorageConf(t, fs, &filer_pb.FilerConf_PathConf{
		LocationPrefix:    "/buckets/video/",
		MaxFileNameLength: 8,
	})

	now := time.Unix(1700000000, 0)

	resp, err := fs.ObjectTransaction(context.Background(), &filer_pb.ObjectTransactionRequest{
		LockKey: "/buckets/video/toolong.mp4",
		Mutations: []*filer_pb.ObjectMutation{
			{
				Type:      filer_pb.ObjectMutation_PUT,
				Directory: "/buckets/video",
				Entry: &filer_pb.Entry{
					Name: "toolong.mp4",
					Attributes: &filer_pb.FuseAttributes{
						Crtime:   now.Unix(),
						Mtime:    now.Unix(),
						FileMode: 0644,
						FileSize: 123,
					},
				},
			},
		},
	})
	if err != nil {
		t.Fatalf("ObjectTransaction transport error: %v", err)
	}
	if resp.Error == "" {
		t.Fatalf("ObjectTransaction should reject a name longer than the rule's max_file_name_length")
	}

	if _, ok := store.entries["/buckets/video/toolong.mp4"]; ok {
		t.Fatalf("over-long name must not create the entry")
	}
}

func TestObjectTransactionPutFailsOnReadOnlyStorageConfig(t *testing.T) {
	// An explicit TTL must not bypass the read-only rule.
	for _, ttlSec := range []int32{0, 7200} {
		fs, store := newTxnTestServer(t, nil)

		mustSetObjectTxnStorageConf(t, fs, &filer_pb.FilerConf_PathConf{
			LocationPrefix: "/buckets/video/",
			ReadOnly:       true,
		})

		now := time.Unix(1700000000, 0)

		resp, err := fs.ObjectTransaction(context.Background(), &filer_pb.ObjectTransactionRequest{
			LockKey: "/buckets/video/readonly.mp4",
			Mutations: []*filer_pb.ObjectMutation{
				{
					Type:      filer_pb.ObjectMutation_PUT,
					Directory: "/buckets/video",
					Entry: &filer_pb.Entry{
						Name: "readonly.mp4",
						Attributes: &filer_pb.FuseAttributes{
							Crtime:   now.Unix(),
							Mtime:    now.Unix(),
							FileMode: 0644,
							FileSize: 123,
							TtlSec:   ttlSec,
						},
					},
				},
			},
		})
		if err != nil {
			t.Fatalf("TtlSec=%d: ObjectTransaction transport error: %v", ttlSec, err)
		}
		if resp.Error == "" {
			t.Fatalf("TtlSec=%d: ObjectTransaction should report read-only storage config error", ttlSec)
		}

		if _, ok := store.entries["/buckets/video/readonly.mp4"]; ok {
			t.Fatalf("TtlSec=%d: read-only storage config must not create the entry", ttlSec)
		}
	}
}
