//  Copyright (c) 2026 Couchbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 		http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
	"fmt"
	"os"

	bolt "go.etcd.io/bbolt"
)

// All of the bolt impls provide a layer of indirection to allow for processing
// of values as they are read/written to bolt depending on the key or bucket name
// This is used to allow better support for file callbacks

// wrapper around bolt.DB
type RootBoltImpl struct {
	*bolt.DB
}

// wrapper around bolt.Tx
type BoltTxImpl struct {
	*bolt.Tx
}

// wrapper around bolt.Bucket
type BoltBucketImpl struct {
	*bolt.Bucket

	name string // store the name of the bucket during creation
}

func OpenBolt(path string, mode os.FileMode, options *bolt.Options) (*RootBoltImpl, error) {
	db, err := bolt.Open(path, mode, options)
	if err != nil {
		return nil, err
	}
	return &RootBoltImpl{DB: db}, nil
}

func (r *RootBoltImpl) Begin(writable bool) (*BoltTxImpl, error) {
	tx, err := r.DB.Begin(writable)
	if err != nil {
		return nil, err
	}
	return &BoltTxImpl{Tx: tx}, nil
}

func (r *RootBoltImpl) View(fn func(*BoltTxImpl) error) error {
	return r.DB.View(func(tx *bolt.Tx) error {
		return fn(&BoltTxImpl{Tx: tx})
	})
}

func (r *RootBoltImpl) Update(fn func(*BoltTxImpl) error) error {
	return r.DB.Update(func(tx *bolt.Tx) error {
		return fn(&BoltTxImpl{Tx: tx})
	})
}

func (tx *BoltTxImpl) CreateBucketIfNotExists(name []byte) (*BoltBucketImpl, error) {
	bucket, err := tx.Tx.CreateBucketIfNotExists(name)
	if err != nil {
		return nil, err
	}
	return &BoltBucketImpl{
		name:   string(name),
		Bucket: bucket,
	}, nil
}

func (tx *BoltTxImpl) Bucket(name []byte) *BoltBucketImpl {
	bucket := tx.Tx.Bucket(name)
	if bucket == nil {
		return nil
	}
	return &BoltBucketImpl{
		name:   string(name),
		Bucket: bucket,
	}
}

func (b *BoltBucketImpl) GetBucket(name []byte) *BoltBucketImpl {
	bucket := b.Bucket.Bucket(name)
	if bucket == nil {
		return nil
	}
	return &BoltBucketImpl{
		name:   string(name),
		Bucket: bucket,
	}
}

func (b *BoltBucketImpl) CreateBucketIfNotExists(name []byte) (*BoltBucketImpl, error) {
	bucket, err := b.Bucket.CreateBucketIfNotExists(name)
	if err != nil {
		return nil, err
	}
	return &BoltBucketImpl{
		name:   string(name),
		Bucket: bucket,
	}, nil
}

// Process values during ForEach if the bucket name or key is in the boltKeysProcessed map
func (b *BoltBucketImpl) ForEach(fn func(key []byte, value []byte) error, reader FileReader) error {
	_, ok1 := boltKeysProcessed[b.name]
	return b.Bucket.ForEach(func(k, v []byte) error {
		v = append([]byte(nil), v...)
		if _, ok2 := boltKeysProcessed[string(k)]; ok1 || ok2 {
			if reader == nil {
				return fmt.Errorf("reader callback is required for bucket %s", b.name)
			}
			processedValue, err := reader.Process(v)
			if err != nil {
				return err
			}
			return fn(k, processedValue)
		}
		return fn(k, v)
	})
}

// Process values during Put/Get if the bucket name or key is in the boltKeysProcessed map
func (b *BoltBucketImpl) Put(key []byte, value []byte, writer FileWriter) error {
	_, ok1 := boltKeysProcessed[string(key)]
	_, ok2 := boltKeysProcessed[b.name]
	value = append([]byte(nil), value...)
	if ok1 || ok2 {
		if writer == nil {
			return fmt.Errorf("writer callback is required for key %s", string(key))
		}
		processedValue := writer.Process(value)
		return b.Bucket.Put(key, processedValue)
	}
	return b.Bucket.Put(key, value)
}

// Process values during Put/Get if the bucket name or key is in the boltKeysProcessed map
func (b *BoltBucketImpl) Get(key []byte, reader FileReader) ([]byte, error) {
	_, ok1 := boltKeysProcessed[string(key)]
	_, ok2 := boltKeysProcessed[b.name]
	if ok1 || ok2 {
		if reader == nil {
			return nil, fmt.Errorf("reader callback is required for key %s", string(key))
		}
		val := b.Bucket.Get(key)
		if val == nil {
			return nil, nil
		}
		processedVal, err := reader.Process(val)
		if err != nil {
			return nil, err
		}
		return processedVal, nil
	}
	return b.Bucket.Get(key), nil
}
