package uring

import (
	"context"
	"os"

	"github.com/iceber/iouring-go"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
)

var tracer = otel.Tracer("github.com/royalcat/tstor/pkg/uring")

type FS struct {
	ur *iouring.IOURing
}

func NewFS(ur *iouring.IOURing) *FS {
	return &FS{
		ur: ur,
	}
}

func (o *FS) OpenFile(ctx context.Context, name string) (File, error) {
	ctx, span := tracer.Start(ctx, "uring.FS.OpenFile", trace.WithAttributes(attribute.String("name", name)))
	defer span.End()

	f, err := os.Open(name)
	if err != nil {
		return File{}, err
	}

	return File{
		ur: o.ur,
		f:  f,
	}, nil
}

func NewFile(ur *iouring.IOURing, f *os.File) *File {
	return &File{
		ur: ur,
		f:  f,
	}
}

type File struct {
	ur *iouring.IOURing
	f  *os.File
}

func (o *File) pread(ctx context.Context, b []byte, off uint64) (int, error) {
	ctx, span := tracer.Start(ctx, "uring.File.pread", trace.WithAttributes(attribute.Int("size", len(b))))
	defer span.End()

	req, err := o.ur.Pread(o.f, b, off, nil)
	if err != nil {
		return 0, err
	}

	select {
	case <-req.Done():
		return req.GetRes()
	case <-ctx.Done():
		if _, err := req.Cancel(); err != nil {
			return 0, err
		}
		<-req.Done()
		return 0, ctx.Err()
	}
}

func (f *File) ReadAt(ctx context.Context, b []byte, off int64) (n int, err error) {
	ctx, span := tracer.Start(ctx, "uring.File.ReadAt", trace.WithAttributes(attribute.Int("size", len(b))))
	defer span.End()

	return f.f.ReadAt(b, off)

	for len(b) > 0 {
		if ctx.Err() != nil {
			err = ctx.Err()
			break
		}

		m, e := f.pread(ctx, b, uint64(off))
		if e != nil {
			err = e
			break
		}
		n += m
		b = b[m:]
		off += int64(m)
	}

	return n, err
}

func (o *File) Close(ctx context.Context) error {
	return o.f.Close()
}

func waitRequest(ctx context.Context, req iouring.Request) (int, error) {
	select {
	case <-req.Done():
		return req.GetRes()
	case <-ctx.Done():
		if _, err := req.Cancel(); err != nil {
			return 0, err
		}
		return 0, ctx.Err()
	}
}