package common

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"sync"

	"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
	"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models/auth"
	"github.com/google/uuid"
)

type RequestPool struct {
	sp sync.Pool
}

func (p *RequestPool) Get() *Request {
	r, _ := p.sp.Get().(*Request)
	return r
}

func (p *RequestPool) Put(r *Request) {
	r.ID = ""
	r.Metadata = &sync.Map{}
	r.ResolveValues = &sync.Map{}
	r.Session = nil
	r.User = nil
	r.Body = nil
	p.sp.Put(r)
}

func NewRequestPool() *RequestPool {
	return &RequestPool{
		sp: sync.Pool{
			New: func() any {
				return &Request{
					ResolveValues: &sync.Map{},
					Metadata:      &sync.Map{},
				}
			},
		},
	}
}

type Request struct {
	ID      string
	Session *auth.Session
	User    *auth.User
	// ResolveValues - data required to process request.
	ResolveValues *sync.Map
	// Metadata - an additional data, usually added with preprocessing.
	Metadata *sync.Map
	// Request body
	Body []byte
}

// NewRequestFromHttp builds a new *Request struct from raw http Request. No auth data validated.
func NewRequestFromHttp(pool *RequestPool, req *http.Request) *Request {
	out := pool.sp.Get().(*Request)

	cookies := req.Cookies()
	headers := req.Header

	out.Metadata = &sync.Map{}

	for _, cookie := range cookies {
		out.Metadata.Store(cookie.Name, cookie.Value)
	}

	for hname, hval := range headers {
		out.Metadata.Store(hname, hval)
	}

	body, err := io.ReadAll(req.Body)
	if err != nil {
		logger.Error(context.TODO(), "failed to read request body", logger.Err(err))
	}
	out.Body = body

	reqID := uuid.NewString()
	out.ID = reqID
	return out
}

func GetValue[T any](vals *sync.Map, key string) (T, error) {
	var out T
	if vals == nil {
		return out, fmt.Errorf("nil vals map")
	}
	rawVal, ok := vals.Load(key)
	if !ok {
		return out, fmt.Errorf("value not found in resolve values set")
	}

	out, ok = rawVal.(T)
	if !ok {
		return out, fmt.Errorf("type of a value is unexpected")
	}

	return out, nil
}
