package dash

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/seaweedfs/seaweedfs/weed/glog"
	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
	"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables"
)

// S3Tables data structures for admin UI

type S3TablesBucketsData struct {
	Username     string                  `json:"username"`
	Buckets      []S3TablesBucketSummary `json:"buckets"`
	TotalBuckets int                     `json:"total_buckets"`
	IcebergPort  int                     `json:"iceberg_port"`
	LastUpdated  time.Time               `json:"last_updated"`
}

type S3TablesBucketSummary struct {
	ARN            string    `json:"arn"`
	Name           string    `json:"name"`
	OwnerAccountID string    `json:"ownerAccountId"`
	CreatedAt      time.Time `json:"createdAt"`
}

type S3TablesNamespacesData struct {
	Username        string                      `json:"username"`
	BucketARN       string                      `json:"bucket_arn"`
	Namespaces      []s3tables.NamespaceSummary `json:"namespaces"`
	TotalNamespaces int                         `json:"total_namespaces"`
	LastUpdated     time.Time                   `json:"last_updated"`
}

type S3TablesTablesData struct {
	Username    string                  `json:"username"`
	BucketARN   string                  `json:"bucket_arn"`
	Namespace   string                  `json:"namespace"`
	Tables      []s3tables.TableSummary `json:"tables"`
	TotalTables int                     `json:"total_tables"`
	LastUpdated time.Time               `json:"last_updated"`
}

type tableBucketMetadata struct {
	Name           string    `json:"name"`
	CreatedAt      time.Time `json:"createdAt"`
	OwnerAccountID string    `json:"ownerAccountId"`
}

// S3Tables manager helpers

const s3TablesAdminListLimit = 1000

func parseNamespaceInput(namespace string) ([]string, error) {
	return s3tables.ParseNamespace(namespace)
}

func (s *AdminServer) parseNamespaceFromRequest(w http.ResponseWriter, namespace string) ([]string, bool) {
	parts, err := parseNamespaceInput(namespace)
	if err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid namespace: "+err.Error())
		return nil, false
	}
	return parts, true
}

func newS3TablesManager() *s3tables.Manager {
	manager := s3tables.NewManager()
	manager.SetAccountID(s3_constants.AccountAdminId)
	return manager
}

func (s *AdminServer) executeS3TablesOperation(ctx context.Context, operation string, req interface{}, resp interface{}) error {
	return s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		mgrClient := s3tables.NewManagerClient(client)
		return s.s3TablesManager.Execute(ctx, mgrClient, operation, req, resp, s3_constants.AccountAdminId)
	})
}

// S3Tables data retrieval for pages

func (s *AdminServer) GetS3TablesBucketsData(ctx context.Context) (S3TablesBucketsData, error) {
	var buckets []S3TablesBucketSummary
	err := s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		resp, err := client.ListEntries(ctx, &filer_pb.ListEntriesRequest{
			Directory:          s3tables.TablesPath,
			Limit:              uint32(s3TablesAdminListLimit * 2),
			InclusiveStartFrom: true,
		})
		if err != nil {
			return err
		}
		for len(buckets) < s3TablesAdminListLimit {
			entry, recvErr := resp.Recv()
			if recvErr != nil {
				if recvErr == io.EOF {
					break
				}
				return recvErr
			}
			if entry.Entry == nil || !entry.Entry.IsDirectory {
				continue
			}
			if strings.HasPrefix(entry.Entry.Name, ".") {
				continue
			}
			if !s3tables.IsTableBucketEntry(entry.Entry) {
				continue
			}
			metaBytes, ok := entry.Entry.Extended[s3tables.ExtendedKeyMetadata]
			if !ok {
				continue
			}
			var metadata tableBucketMetadata
			if err := json.Unmarshal(metaBytes, &metadata); err != nil {
				glog.V(1).Infof("S3Tables: failed to decode table bucket metadata for %s: %v", entry.Entry.Name, err)
				continue
			}
			arn, err := s3tables.BuildBucketARN(s3tables.DefaultRegion, metadata.OwnerAccountID, entry.Entry.Name)
			if err != nil {
				glog.V(1).Infof("S3Tables: failed to build table bucket ARN for %s: %v", entry.Entry.Name, err)
				continue
			}
			buckets = append(buckets, S3TablesBucketSummary{
				ARN:            arn,
				Name:           entry.Entry.Name,
				OwnerAccountID: metadata.OwnerAccountID,
				CreatedAt:      metadata.CreatedAt,
			})
		}
		return nil
	})
	if err != nil {
		return S3TablesBucketsData{}, err
	}
	return S3TablesBucketsData{
		Buckets:      buckets,
		TotalBuckets: len(buckets),
		IcebergPort:  s.icebergPort,
		LastUpdated:  time.Now(),
	}, nil
}

func (s *AdminServer) GetS3TablesNamespacesData(ctx context.Context, bucketArn string) (S3TablesNamespacesData, error) {
	var resp s3tables.ListNamespacesResponse
	req := &s3tables.ListNamespacesRequest{TableBucketARN: bucketArn, MaxNamespaces: s3TablesAdminListLimit}
	if err := s.executeS3TablesOperation(ctx, "ListNamespaces", req, &resp); err != nil {
		return S3TablesNamespacesData{}, err
	}
	return S3TablesNamespacesData{
		BucketARN:       bucketArn,
		Namespaces:      resp.Namespaces,
		TotalNamespaces: len(resp.Namespaces),
		LastUpdated:     time.Now(),
	}, nil
}

func (s *AdminServer) GetS3TablesTablesData(ctx context.Context, bucketArn, namespace string) (S3TablesTablesData, error) {
	var resp s3tables.ListTablesResponse
	var ns []string
	if namespace != "" {
		parts, err := parseNamespaceInput(namespace)
		if err != nil {
			return S3TablesTablesData{}, err
		}
		ns = parts
	}
	req := &s3tables.ListTablesRequest{TableBucketARN: bucketArn, Namespace: ns, MaxTables: s3TablesAdminListLimit}
	if err := s.executeS3TablesOperation(ctx, "ListTables", req, &resp); err != nil {
		return S3TablesTablesData{}, err
	}
	return S3TablesTablesData{
		BucketARN:   bucketArn,
		Namespace:   namespace,
		Tables:      resp.Tables,
		TotalTables: len(resp.Tables),
		LastUpdated: time.Now(),
	}, nil
}

// Iceberg Catalog data providers

// GetIcebergCatalogData returns the Iceberg catalog overview data.
// Each S3 Table Bucket is exposed as an Iceberg catalog.
func (s *AdminServer) GetIcebergCatalogData(ctx context.Context) (IcebergCatalogData, error) {
	bucketsData, err := s.GetS3TablesBucketsData(ctx)
	if err != nil {
		return IcebergCatalogData{}, err
	}

	catalogs := make([]IcebergCatalogInfo, 0, len(bucketsData.Buckets))
	for _, bucket := range bucketsData.Buckets {
		catalogs = append(catalogs, IcebergCatalogInfo{
			Name:           bucket.Name,
			ARN:            bucket.ARN,
			OwnerAccountID: bucket.OwnerAccountID,
			CreatedAt:      bucket.CreatedAt,
		})
	}

	return IcebergCatalogData{
		Catalogs:      catalogs,
		TotalCatalogs: len(catalogs),
		IcebergPort:   s.icebergPort, // Use the port passed to AdminServer
		LastUpdated:   time.Now(),
	}, nil
}

// GetIcebergNamespacesData returns namespaces for an Iceberg catalog.
func (s *AdminServer) GetIcebergNamespacesData(ctx context.Context, catalogName, bucketArn string) (IcebergNamespacesData, error) {
	nsData, err := s.GetS3TablesNamespacesData(ctx, bucketArn)
	if err != nil {
		return IcebergNamespacesData{}, err
	}

	namespaces := make([]IcebergNamespaceInfo, 0, len(nsData.Namespaces))
	for _, ns := range nsData.Namespaces {
		name := ""
		if len(ns.Namespace) > 0 {
			name = strings.Join(ns.Namespace, ".")
		}
		namespaces = append(namespaces, IcebergNamespaceInfo{
			Name:      name,
			CreatedAt: ns.CreatedAt,
		})
	}

	return IcebergNamespacesData{
		CatalogName:     catalogName,
		BucketARN:       bucketArn,
		Namespaces:      namespaces,
		TotalNamespaces: len(namespaces),
		LastUpdated:     time.Now(),
	}, nil
}

// GetIcebergTablesData returns tables for an Iceberg namespace.
func (s *AdminServer) GetIcebergTablesData(ctx context.Context, catalogName, bucketArn, namespace string) (IcebergTablesData, error) {
	tablesData, err := s.GetS3TablesTablesData(ctx, bucketArn, namespace)
	if err != nil {
		return IcebergTablesData{}, err
	}

	tables := make([]IcebergTableInfo, 0, len(tablesData.Tables))
	for _, t := range tablesData.Tables {
		tables = append(tables, IcebergTableInfo{
			Name:      t.Name,
			CreatedAt: t.CreatedAt,
		})
	}

	return IcebergTablesData{
		CatalogName:   catalogName,
		NamespaceName: namespace,
		BucketARN:     bucketArn,
		Tables:        tables,
		TotalTables:   len(tables),
		LastUpdated:   time.Now(),
	}, nil
}

// GetIcebergTableDetailsData returns Iceberg table metadata and snapshot information.
func (s *AdminServer) GetIcebergTableDetailsData(ctx context.Context, catalogName, bucketArn, namespace, tableName string) (IcebergTableDetailsData, error) {
	var resp s3tables.GetTableResponse
	namespaceParts, err := parseNamespaceInput(namespace)
	if err != nil {
		return IcebergTableDetailsData{}, err
	}
	req := &s3tables.GetTableRequest{
		TableBucketARN: bucketArn,
		Namespace:      namespaceParts,
		Name:           tableName,
	}
	if err := s.executeS3TablesOperation(ctx, "GetTable", req, &resp); err != nil {
		return IcebergTableDetailsData{}, err
	}

	details := IcebergTableDetailsData{
		CatalogName:      catalogName,
		NamespaceName:    namespace,
		TableName:        resp.Name,
		BucketARN:        bucketArn,
		TableARN:         resp.TableARN,
		Format:           resp.Format,
		CreatedAt:        resp.CreatedAt,
		ModifiedAt:       resp.ModifiedAt,
		MetadataLocation: resp.MetadataLocation,
	}

	applyIcebergMetadata(resp.Metadata, &details)
	return details, nil
}

type icebergFullMetadata struct {
	FormatVersion     int                    `json:"format-version"`
	TableUUID         string                 `json:"table-uuid"`
	Location          string                 `json:"location"`
	LastUpdatedMs     int64                  `json:"last-updated-ms"`
	Schemas           []icebergSchema        `json:"schemas"`
	Schema            *icebergSchema         `json:"schema"`
	CurrentSchemaID   int                    `json:"current-schema-id"`
	PartitionSpecs    []icebergPartitionSpec `json:"partition-specs"`
	PartitionSpec     *icebergPartitionSpec  `json:"partition-spec"`
	DefaultSpecID     int                    `json:"default-spec-id"`
	Properties        map[string]string      `json:"properties"`
	Snapshots         []icebergSnapshot      `json:"snapshots"`
	CurrentSnapshotID int64                  `json:"current-snapshot-id"`
}

type icebergSchema struct {
	SchemaID int                  `json:"schema-id"`
	Fields   []icebergSchemaField `json:"fields"`
}

type icebergSchemaField struct {
	ID       int             `json:"id"`
	Name     string          `json:"name"`
	Type     json.RawMessage `json:"type"`
	Required bool            `json:"required"`
}

type icebergPartitionSpec struct {
	SpecID int                     `json:"spec-id"`
	Fields []icebergPartitionField `json:"fields"`
}

type icebergPartitionField struct {
	SourceID  int    `json:"source-id"`
	FieldID   int    `json:"field-id"`
	Name      string `json:"name"`
	Transform string `json:"transform"`
}

type icebergSnapshot struct {
	SnapshotID   int64             `json:"snapshot-id"`
	TimestampMs  int64             `json:"timestamp-ms"`
	ManifestList string            `json:"manifest-list"`
	Summary      map[string]string `json:"summary"`
}

func applyIcebergMetadata(metadata *s3tables.TableMetadata, details *IcebergTableDetailsData) {
	if details == nil || metadata == nil {
		return
	}
	if len(metadata.FullMetadata) == 0 {
		details.SchemaFields = schemaFieldsFromIceberg(metadata.Iceberg)
		return
	}

	var full icebergFullMetadata
	if err := json.Unmarshal(metadata.FullMetadata, &full); err != nil {
		glog.V(1).Infof("iceberg metadata parse failed: %v", err)
		details.MetadataError = fmt.Sprintf("Failed to parse Iceberg metadata: %v", err)
		details.SchemaFields = schemaFieldsFromIceberg(metadata.Iceberg)
		return
	}

	details.TableLocation = full.Location
	details.SchemaFields = schemaFieldsFromFullMetadata(full, metadata.Iceberg)
	details.PartitionFields = partitionFieldsFromFullMetadata(full)
	details.Properties = propertiesFromFullMetadata(full.Properties)
	details.Snapshots = snapshotsFromFullMetadata(full.Snapshots)
	details.SnapshotCount = len(full.Snapshots)
	details.HasSnapshotCount = true
	if metricsSnapshot := selectSnapshotForMetrics(full); metricsSnapshot != nil {
		if value, ok := parseSummaryInt(metricsSnapshot.Summary, "total-data-files", "total-data-file-count", "total-files", "total-file-count"); ok {
			details.DataFileCount = value
			details.HasDataFileCount = true
		}
		if value, ok := parseSummaryInt(metricsSnapshot.Summary, "total-files-size", "total-data-files-size", "total-file-size", "total-data-file-size", "total-data-size", "total-size"); ok {
			details.TotalSizeBytes = value
			details.HasTotalSize = true
		}
	}
}

func typeToString(t json.RawMessage) json.RawMessage {
	if t == nil || len(t) == 0 {
		return json.RawMessage(`(complex)`)
	}
	var primitive string
	if err := json.Unmarshal(t, &primitive); err == nil {
		return json.RawMessage(primitive)
	}
	var v interface{}
	if err := json.Unmarshal(t, &v); err != nil {
		return json.RawMessage(`(complex)`)
	}
	return json.RawMessage(`(complex)`)
}

func schemaFieldsFromFullMetadata(full icebergFullMetadata, fallback *s3tables.IcebergMetadata) []IcebergSchemaFieldInfo {
	if schema := selectSchema(full); schema != nil {
		fields := make([]IcebergSchemaFieldInfo, 0, len(schema.Fields))
		for _, field := range schema.Fields {
			fields = append(fields, IcebergSchemaFieldInfo{
				ID:       field.ID,
				Name:     field.Name,
				Type:     typeToString(field.Type),
				Required: field.Required,
			})
		}
		return fields
	}
	return schemaFieldsFromIceberg(fallback)
}

func schemaFieldsFromIceberg(metadata *s3tables.IcebergMetadata) []IcebergSchemaFieldInfo {
	if metadata == nil {
		return nil
	}
	fields := make([]IcebergSchemaFieldInfo, 0, len(metadata.Schema.Fields))
	for _, field := range metadata.Schema.Fields {
		typeBytes, err := json.Marshal(field.Type)
		if err != nil {
			typeBytes = json.RawMessage(`(complex)`)
		} else {
			typeBytes = typeToString(typeBytes)
		}
		fields = append(fields, IcebergSchemaFieldInfo{
			Name:     field.Name,
			Type:     typeBytes,
			Required: field.Required,
		})
	}
	return fields
}

func selectSchema(full icebergFullMetadata) *icebergSchema {
	if len(full.Schemas) == 0 && full.Schema == nil {
		return nil
	}
	if len(full.Schemas) == 0 {
		return full.Schema
	}
	for i := range full.Schemas {
		if full.Schemas[i].SchemaID == full.CurrentSchemaID {
			return &full.Schemas[i]
		}
	}
	return &full.Schemas[0]
}

func partitionFieldsFromFullMetadata(full icebergFullMetadata) []IcebergPartitionFieldInfo {
	var spec *icebergPartitionSpec
	if len(full.PartitionSpecs) == 0 && full.PartitionSpec == nil {
		return nil
	}
	if len(full.PartitionSpecs) == 0 {
		spec = full.PartitionSpec
	} else {
		for i := range full.PartitionSpecs {
			if full.PartitionSpecs[i].SpecID == full.DefaultSpecID {
				spec = &full.PartitionSpecs[i]
				break
			}
		}
		if spec == nil {
			spec = &full.PartitionSpecs[0]
		}
	}
	if spec == nil {
		return nil
	}
	fields := make([]IcebergPartitionFieldInfo, 0, len(spec.Fields))
	for _, field := range spec.Fields {
		fields = append(fields, IcebergPartitionFieldInfo{
			Name:      field.Name,
			Transform: field.Transform,
			SourceID:  field.SourceID,
			FieldID:   field.FieldID,
		})
	}
	return fields
}

func propertiesFromFullMetadata(properties map[string]string) []IcebergPropertyInfo {
	if len(properties) == 0 {
		return nil
	}
	keys := make([]string, 0, len(properties))
	for key := range properties {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	entries := make([]IcebergPropertyInfo, 0, len(keys))
	for _, key := range keys {
		entries = append(entries, IcebergPropertyInfo{Key: key, Value: properties[key]})
	}
	return entries
}

func snapshotsFromFullMetadata(snapshots []icebergSnapshot) []IcebergSnapshotInfo {
	if len(snapshots) == 0 {
		return nil
	}
	sorted := make([]icebergSnapshot, len(snapshots))
	copy(sorted, snapshots)
	sort.Slice(sorted, func(i, j int) bool {
		return sorted[i].TimestampMs > sorted[j].TimestampMs
	})
	info := make([]IcebergSnapshotInfo, 0, len(sorted))
	for _, snapshot := range sorted {
		operation := ""
		if snapshot.Summary != nil {
			operation = snapshot.Summary["operation"]
		}
		timestamp := time.Time{}
		if snapshot.TimestampMs > 0 {
			timestamp = time.Unix(0, snapshot.TimestampMs*int64(time.Millisecond))
		}
		info = append(info, IcebergSnapshotInfo{
			SnapshotID:   snapshot.SnapshotID,
			Timestamp:    timestamp,
			Operation:    operation,
			ManifestList: snapshot.ManifestList,
		})
	}
	return info
}

func selectSnapshotForMetrics(full icebergFullMetadata) *icebergSnapshot {
	if len(full.Snapshots) == 0 {
		return nil
	}
	for i := range full.Snapshots {
		if full.Snapshots[i].SnapshotID == full.CurrentSnapshotID {
			return &full.Snapshots[i]
		}
	}
	latestIdx := 0
	for i := 1; i < len(full.Snapshots); i++ {
		if full.Snapshots[i].TimestampMs > full.Snapshots[latestIdx].TimestampMs {
			latestIdx = i
		}
	}
	return &full.Snapshots[latestIdx]
}

func parseSummaryInt(summary map[string]string, keys ...string) (int64, bool) {
	if len(summary) == 0 {
		return 0, false
	}
	for _, key := range keys {
		value, ok := summary[key]
		if !ok || value == "" {
			continue
		}
		parsed, err := strconv.ParseInt(value, 10, 64)
		if err != nil {
			continue
		}
		return parsed, true
	}
	return 0, false
}

// API handlers

func (s *AdminServer) ListS3TablesBucketsAPI(w http.ResponseWriter, r *http.Request) {
	data, err := s.GetS3TablesBucketsData(r.Context())
	if err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, data)
}

func (s *AdminServer) CreateS3TablesBucket(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	var req struct {
		Name  string            `json:"name"`
		Tags  map[string]string `json:"tags"`
		Owner string            `json:"owner"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}
	if req.Name == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket name is required")
		return
	}
	owner := strings.TrimSpace(req.Owner)
	if len(owner) > MaxOwnerNameLength {
		writeJSONError(w, http.StatusBadRequest, fmt.Sprintf("Owner name must be %d characters or less", MaxOwnerNameLength))
		return
	}
	if len(req.Tags) > 0 {
		if err := s3tables.ValidateTags(req.Tags); err != nil {
			writeJSONError(w, http.StatusBadRequest, "Invalid tags: "+err.Error())
			return
		}
	}
	createReq := &s3tables.CreateTableBucketRequest{Name: req.Name, Tags: req.Tags}
	var resp s3tables.CreateTableBucketResponse
	if err := s.executeS3TablesOperation(r.Context(), "CreateTableBucket", createReq, &resp); err != nil {
		writeS3TablesError(w, err)
		return
	}
	if owner != "" {
		if err := s.SetTableBucketOwner(r.Context(), req.Name, owner); err != nil {
			deleteReq := &s3tables.DeleteTableBucketRequest{TableBucketARN: resp.ARN}
			if deleteErr := s.executeS3TablesOperation(r.Context(), "DeleteTableBucket", deleteReq, nil); deleteErr != nil {
				writeJSONError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to set table bucket owner: %v; rollback delete failed: %v", err, deleteErr))
				return
			}
			writeS3TablesError(w, err)
			return
		}
	}
	writeJSON(w, http.StatusCreated, map[string]interface{}{"arn": resp.ARN})
}

func (s *AdminServer) SetTableBucketOwner(ctx context.Context, bucketName, owner string) error {
	return s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
		resp, err := client.LookupDirectoryEntry(ctx, &filer_pb.LookupDirectoryEntryRequest{
			Directory: s3tables.TablesPath,
			Name:      bucketName,
		})
		if err != nil {
			return fmt.Errorf("lookup table bucket %s: %w", bucketName, err)
		}
		if resp.Entry == nil {
			return fmt.Errorf("table bucket %s not found", bucketName)
		}
		entry := resp.Entry
		if entry.Extended == nil {
			return fmt.Errorf("table bucket %s metadata missing", bucketName)
		}
		metaBytes, ok := entry.Extended[s3tables.ExtendedKeyMetadata]
		if !ok {
			return fmt.Errorf("table bucket %s metadata missing", bucketName)
		}
		var metadata tableBucketMetadata
		if err := json.Unmarshal(metaBytes, &metadata); err != nil {
			return fmt.Errorf("failed to parse table bucket metadata: %w", err)
		}
		metadata.OwnerAccountID = owner
		updated, err := json.Marshal(&metadata)
		if err != nil {
			return fmt.Errorf("failed to marshal table bucket metadata: %w", err)
		}
		entry.Extended[s3tables.ExtendedKeyMetadata] = updated
		if _, err := client.UpdateEntry(ctx, &filer_pb.UpdateEntryRequest{
			Directory: s3tables.TablesPath,
			Entry:     entry,
		}); err != nil {
			return fmt.Errorf("failed to update table bucket owner: %w", err)
		}
		return nil
	})
}

func (s *AdminServer) DeleteS3TablesBucket(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	bucketArn := r.URL.Query().Get("bucket")
	if bucketArn == "" {
		writeJSONError(w, http.StatusBadRequest, "Bucket ARN is required")
		return
	}
	req := &s3tables.DeleteTableBucketRequest{TableBucketARN: bucketArn}
	if err := s.executeS3TablesOperation(r.Context(), "DeleteTableBucket", req, nil); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Bucket deleted"})
}

func (s *AdminServer) ListS3TablesNamespacesAPI(w http.ResponseWriter, r *http.Request) {
	bucketArn := r.URL.Query().Get("bucket")
	if bucketArn == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket query parameter is required")
		return
	}
	data, err := s.GetS3TablesNamespacesData(r.Context(), bucketArn)
	if err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, data)
}

func (s *AdminServer) CreateS3TablesNamespace(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	var req struct {
		BucketARN string `json:"bucket_arn"`
		Name      string `json:"name"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}
	if req.BucketARN == "" || req.Name == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket_arn and name are required")
		return
	}
	namespaceParts, ok := s.parseNamespaceFromRequest(w, req.Name)
	if !ok {
		return
	}
	createReq := &s3tables.CreateNamespaceRequest{TableBucketARN: req.BucketARN, Namespace: namespaceParts}
	var resp s3tables.CreateNamespaceResponse
	if err := s.executeS3TablesOperation(r.Context(), "CreateNamespace", createReq, &resp); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, map[string]interface{}{"namespace": resp.Namespace})
}

func (s *AdminServer) DeleteS3TablesNamespace(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	bucketArn := r.URL.Query().Get("bucket")
	namespace := r.URL.Query().Get("name")
	if bucketArn == "" || namespace == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket and name query parameters are required")
		return
	}
	namespaceParts, ok := s.parseNamespaceFromRequest(w, namespace)
	if !ok {
		return
	}
	req := &s3tables.DeleteNamespaceRequest{TableBucketARN: bucketArn, Namespace: namespaceParts}
	if err := s.executeS3TablesOperation(r.Context(), "DeleteNamespace", req, nil); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Namespace deleted"})
}

func (s *AdminServer) ListS3TablesTablesAPI(w http.ResponseWriter, r *http.Request) {
	bucketArn := r.URL.Query().Get("bucket")
	if bucketArn == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket query parameter is required")
		return
	}
	namespace := r.URL.Query().Get("namespace")
	data, err := s.GetS3TablesTablesData(r.Context(), bucketArn, namespace)
	if err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, data)
}

func (s *AdminServer) CreateS3TablesTable(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	var req struct {
		BucketARN string                  `json:"bucket_arn"`
		Namespace string                  `json:"namespace"`
		Name      string                  `json:"name"`
		Format    string                  `json:"format"`
		Tags      map[string]string       `json:"tags"`
		Metadata  *s3tables.TableMetadata `json:"metadata"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}
	if req.BucketARN == "" || req.Namespace == "" || req.Name == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket_arn, namespace, and name are required")
		return
	}
	namespaceParts, ok := s.parseNamespaceFromRequest(w, req.Namespace)
	if !ok {
		return
	}
	format := req.Format
	if format == "" {
		format = "ICEBERG"
	}
	if len(req.Tags) > 0 {
		if err := s3tables.ValidateTags(req.Tags); err != nil {
			writeJSONError(w, http.StatusBadRequest, "Invalid tags: "+err.Error())
			return
		}
	}
	createReq := &s3tables.CreateTableRequest{
		TableBucketARN: req.BucketARN,
		Namespace:      namespaceParts,
		Name:           req.Name,
		Format:         format,
		Tags:           req.Tags,
		Metadata:       req.Metadata,
	}
	var resp s3tables.CreateTableResponse
	if err := s.executeS3TablesOperation(r.Context(), "CreateTable", createReq, &resp); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, map[string]interface{}{"table_arn": resp.TableARN, "version_token": resp.VersionToken})
}

func (s *AdminServer) DeleteS3TablesTable(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	bucketArn := r.URL.Query().Get("bucket")
	namespace := r.URL.Query().Get("namespace")
	name := r.URL.Query().Get("name")
	version := r.URL.Query().Get("version")
	if bucketArn == "" || namespace == "" || name == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket, namespace, and name query parameters are required")
		return
	}
	namespaceParts, ok := s.parseNamespaceFromRequest(w, namespace)
	if !ok {
		return
	}
	req := &s3tables.DeleteTableRequest{TableBucketARN: bucketArn, Namespace: namespaceParts, Name: name, VersionToken: version}
	if err := s.executeS3TablesOperation(r.Context(), "DeleteTable", req, nil); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Table deleted"})
}

func (s *AdminServer) PutS3TablesBucketPolicy(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	var req struct {
		BucketARN string `json:"bucket_arn"`
		Policy    string `json:"policy"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}
	if req.BucketARN == "" || req.Policy == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket_arn and policy are required")
		return
	}
	putReq := &s3tables.PutTableBucketPolicyRequest{TableBucketARN: req.BucketARN, ResourcePolicy: req.Policy}
	if err := s.executeS3TablesOperation(r.Context(), "PutTableBucketPolicy", putReq, nil); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Policy updated"})
}

func (s *AdminServer) GetS3TablesBucketPolicy(w http.ResponseWriter, r *http.Request) {
	bucketArn := r.URL.Query().Get("bucket")
	if bucketArn == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket query parameter is required")
		return
	}
	getReq := &s3tables.GetTableBucketPolicyRequest{TableBucketARN: bucketArn}
	var resp s3tables.GetTableBucketPolicyResponse
	if err := s.executeS3TablesOperation(r.Context(), "GetTableBucketPolicy", getReq, &resp); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"policy": resp.ResourcePolicy})
}

func (s *AdminServer) DeleteS3TablesBucketPolicy(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	bucketArn := r.URL.Query().Get("bucket")
	if bucketArn == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket query parameter is required")
		return
	}
	deleteReq := &s3tables.DeleteTableBucketPolicyRequest{TableBucketARN: bucketArn}
	if err := s.executeS3TablesOperation(r.Context(), "DeleteTableBucketPolicy", deleteReq, nil); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Policy deleted"})
}

func (s *AdminServer) PutS3TablesTablePolicy(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	var req struct {
		BucketARN string `json:"bucket_arn"`
		Namespace string `json:"namespace"`
		Name      string `json:"name"`
		Policy    string `json:"policy"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}
	if req.BucketARN == "" || req.Namespace == "" || req.Name == "" || req.Policy == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket_arn, namespace, name, and policy are required")
		return
	}
	namespaceParts, ok := s.parseNamespaceFromRequest(w, req.Namespace)
	if !ok {
		return
	}
	putReq := &s3tables.PutTablePolicyRequest{TableBucketARN: req.BucketARN, Namespace: namespaceParts, Name: req.Name, ResourcePolicy: req.Policy}
	if err := s.executeS3TablesOperation(r.Context(), "PutTablePolicy", putReq, nil); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Policy updated"})
}

func (s *AdminServer) GetS3TablesTablePolicy(w http.ResponseWriter, r *http.Request) {
	bucketArn := r.URL.Query().Get("bucket")
	namespace := r.URL.Query().Get("namespace")
	name := r.URL.Query().Get("name")
	if bucketArn == "" || namespace == "" || name == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket, namespace, and name query parameters are required")
		return
	}
	namespaceParts, ok := s.parseNamespaceFromRequest(w, namespace)
	if !ok {
		return
	}
	getReq := &s3tables.GetTablePolicyRequest{TableBucketARN: bucketArn, Namespace: namespaceParts, Name: name}
	var resp s3tables.GetTablePolicyResponse
	if err := s.executeS3TablesOperation(r.Context(), "GetTablePolicy", getReq, &resp); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"policy": resp.ResourcePolicy})
}

func (s *AdminServer) DeleteS3TablesTablePolicy(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	bucketArn := r.URL.Query().Get("bucket")
	namespace := r.URL.Query().Get("namespace")
	name := r.URL.Query().Get("name")
	if bucketArn == "" || namespace == "" || name == "" {
		writeJSONError(w, http.StatusBadRequest, "bucket, namespace, and name query parameters are required")
		return
	}
	namespaceParts, ok := s.parseNamespaceFromRequest(w, namespace)
	if !ok {
		return
	}
	deleteReq := &s3tables.DeleteTablePolicyRequest{TableBucketARN: bucketArn, Namespace: namespaceParts, Name: name}
	if err := s.executeS3TablesOperation(r.Context(), "DeleteTablePolicy", deleteReq, nil); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Policy deleted"})
}

func (s *AdminServer) TagS3TablesResource(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	var req struct {
		ResourceARN string            `json:"resource_arn"`
		Tags        map[string]string `json:"tags"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}
	if req.ResourceARN == "" || len(req.Tags) == 0 {
		writeJSONError(w, http.StatusBadRequest, "resource_arn and tags are required")
		return
	}
	if err := s3tables.ValidateTags(req.Tags); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid tags: "+err.Error())
		return
	}
	tagReq := &s3tables.TagResourceRequest{ResourceARN: req.ResourceARN, Tags: req.Tags}
	if err := s.executeS3TablesOperation(r.Context(), "TagResource", tagReq, nil); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Tags updated"})
}

func (s *AdminServer) ListS3TablesTags(w http.ResponseWriter, r *http.Request) {
	resourceArn := r.URL.Query().Get("arn")
	if resourceArn == "" {
		writeJSONError(w, http.StatusBadRequest, "arn query parameter is required")
		return
	}
	listReq := &s3tables.ListTagsForResourceRequest{ResourceARN: resourceArn}
	var resp s3tables.ListTagsForResourceResponse
	if err := s.executeS3TablesOperation(r.Context(), "ListTagsForResource", listReq, &resp); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, resp)
}

func (s *AdminServer) UntagS3TablesResource(w http.ResponseWriter, r *http.Request) {
	if !requireSessionCSRFToken(w, r) {
		return
	}
	var req struct {
		ResourceARN string   `json:"resource_arn"`
		TagKeys     []string `json:"tag_keys"`
	}
	if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
		writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
		return
	}
	if req.ResourceARN == "" || len(req.TagKeys) == 0 {
		writeJSONError(w, http.StatusBadRequest, "resource_arn and tag_keys are required")
		return
	}
	untagReq := &s3tables.UntagResourceRequest{ResourceARN: req.ResourceARN, TagKeys: req.TagKeys}
	if err := s.executeS3TablesOperation(r.Context(), "UntagResource", untagReq, nil); err != nil {
		writeS3TablesError(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Tags removed"})
}

func parseS3TablesErrorMessage(err error) string {
	if err == nil {
		return ""
	}
	var s3Err *s3tables.S3TablesError
	if errors.As(err, &s3Err) {
		if s3Err.Message != "" {
			return fmt.Sprintf("%s: %s", s3Err.Type, s3Err.Message)
		}
		return s3Err.Type
	}
	return err.Error()
}

func writeS3TablesError(w http.ResponseWriter, err error) {
	writeJSONError(w, s3TablesErrorStatus(err), parseS3TablesErrorMessage(err))
}

func s3TablesErrorStatus(err error) int {
	var s3Err *s3tables.S3TablesError
	if errors.As(err, &s3Err) {
		switch s3Err.Type {
		case s3tables.ErrCodeInvalidRequest:
			return http.StatusBadRequest
		case s3tables.ErrCodeNoSuchBucket, s3tables.ErrCodeNoSuchNamespace, s3tables.ErrCodeNoSuchTable, s3tables.ErrCodeNoSuchPolicy:
			return http.StatusNotFound
		case s3tables.ErrCodeAccessDenied:
			return http.StatusForbidden
		case s3tables.ErrCodeBucketAlreadyExists, s3tables.ErrCodeNamespaceAlreadyExists, s3tables.ErrCodeTableAlreadyExists, s3tables.ErrCodeBucketNotEmpty, s3tables.ErrCodeNamespaceNotEmpty, s3tables.ErrCodeConflict:
			return http.StatusConflict
		}
	}
	return http.StatusInternalServerError
}
