// Copyright (C) MongoDB, Inc. 2023-present.
//
// 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

package mnet

import (
	"context"
	"io"

	"go.mongodb.org/mongo-driver/v2/mongo/address"
	"go.mongodb.org/mongo-driver/v2/x/mongo/driver/description"
)

// ReadWriteCloser represents a Connection where server operations
// can read from, written to, and closed.
type ReadWriteCloser interface {
	Read(ctx context.Context) ([]byte, error)
	Write(ctx context.Context, wm []byte) error
	io.Closer
}

// Describer represents a Connection that can be described.
type Describer interface {
	Description() description.Server
	ID() string
	ServerConnectionID() *int64
	DriverConnectionID() int64
	Address() address.Address
	Stale() bool
	OIDCTokenGenID() uint64
	SetOIDCTokenGenID(uint64)
}

// Streamer represents a Connection that supports streaming wire protocol
// messages using the moreToCome and exhaustAllowed flags.
//
// The SetStreaming and CurrentlyStreaming functions correspond to the
// moreToCome flag on server responses. If a response has moreToCome set,
// SetStreaming(true) will be called and CurrentlyStreaming() should return
// true.
//
// CanStream corresponds to the exhaustAllowed flag. The operations layer will
// set exhaustAllowed on outgoing wire messages to inform the server that the
// driver supports streaming.
type Streamer interface {
	SetStreaming(bool)
	CurrentlyStreaming() bool
	SupportsStreaming() bool
}

// Compressor is an interface used to compress wire messages. If a Connection
// supports compression it should implement this interface as well. The
// CompressWireMessage method will be called during the execution of an
// operation if the wire message is allowed to be compressed.
type Compressor interface {
	CompressWireMessage(src, dst []byte) ([]byte, error)
}

// Pinner represents a Connection that can be pinned by one or more cursors or
// transactions. Implementations of this interface should maintain the following
// invariants:
//
//  1. Each Pin* call should increment the number of references for the
//     connection.
//  2. Each Unpin* call should decrement the number of references for the
//     connection.
//  3. Calls to Close() should be ignored until all resources have unpinned the
//     connection.
type Pinner interface {
	PinToCursor() error
	PinToTransaction() error
	UnpinFromCursor() error
	UnpinFromTransaction() error
}

// Connection represents a connection to a MongoDB server.
type Connection struct {
	ReadWriteCloser
	Describer
	Streamer
	Compressor
	Pinner
}

// NewConnection creates a new Connection with the provided component. This
// constructor returns a component that is already a Connection to avoid
// mis-asserting the composite interfaces.
func NewConnection(component interface {
	ReadWriteCloser
	Describer
},
) *Connection {
	if _, ok := component.(*Connection); ok {
		return component.(*Connection)
	}

	conn := &Connection{
		ReadWriteCloser: component,
	}

	if describer, ok := component.(Describer); ok {
		conn.Describer = describer
	}

	if streamer, ok := component.(Streamer); ok {
		conn.Streamer = streamer
	}

	if compressor, ok := component.(Compressor); ok {
		conn.Compressor = compressor
	}

	if pinner, ok := component.(Pinner); ok {
		conn.Pinner = pinner
	}

	return conn
}
