package item

import (
	"context"
	"database/sql"
	"errors"
	"net/http"
	"time"

	"connectrpc.com/connect"
	"connectrpc.com/validate"
	itemv1 "github.com/spotdemo4/trevstack/server/internal/connect/item/v1"
	"github.com/spotdemo4/trevstack/server/internal/connect/item/v1/itemv1connect"
	"github.com/spotdemo4/trevstack/server/internal/interceptors"
	"github.com/spotdemo4/trevstack/server/internal/sqlc"
	"github.com/spotdemo4/trevstack/server/internal/util"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func itemToConnect(item sqlc.Item) *itemv1.Item {
	timestamp := timestamppb.New(item.Added)

	return &itemv1.Item{
		Id:          item.ID,
		Name:        item.Name,
		Description: item.Description,
		Price:       float32(item.Price),
		Quantity:    int32(item.Quantity),
		Added:       timestamp,
	}
}

type Handler struct {
	db  *sqlc.Queries
	key []byte
}

func (h *Handler) GetItem(ctx context.Context, req *connect.Request[itemv1.GetItemRequest]) (*connect.Response[itemv1.GetItemResponse], error) {
	userid, ok := interceptors.GetUserContext(ctx)
	if !ok {
		return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated"))
	}

	// Get item
	item, err := h.db.GetItem(ctx, sqlc.GetItemParams{
		ID:     req.Msg.Id,
		UserID: userid,
	})
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, connect.NewError(connect.CodeNotFound, err)
		}

		return nil, connect.NewError(connect.CodeInternal, err)
	}

	res := connect.NewResponse(&itemv1.GetItemResponse{
		Item: itemToConnect(item),
	})
	return res, nil
}

func (h *Handler) GetItems(ctx context.Context, req *connect.Request[itemv1.GetItemsRequest]) (*connect.Response[itemv1.GetItemsResponse], error) {
	userid, ok := interceptors.GetUserContext(ctx)
	if !ok {
		return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated"))
	}

	// Verify
	offset := 0
	if req.Msg.Offset != nil {
		offset = int(*req.Msg.Offset)
	}

	limit := 10
	if req.Msg.Limit != nil {
		limit = int(*req.Msg.Limit)
	}

	// Get items
	items, err := h.db.GetItems(ctx, sqlc.GetItemsParams{
		UserID: userid,
		Name:   util.NullLike(req.Msg.Filter),
		Start:  util.NullTimestamp(req.Msg.Start),
		End:    util.NullTimestamp(req.Msg.End),
		Offset: int64(offset),
		Limit:  int64(limit),
	})
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, connect.NewError(connect.CodeNotFound, err)
		}

		return nil, connect.NewError(connect.CodeInternal, err)
	}

	// Get items count
	count, err := h.db.GetItemsCount(ctx, sqlc.GetItemsCountParams{
		UserID: userid,
		Name:   util.NullLike(req.Msg.Filter),
		Start:  util.NullTimestamp(req.Msg.Start),
		End:    util.NullTimestamp(req.Msg.End),
	})
	if err != nil {
		return nil, connect.NewError(connect.CodeInternal, err)
	}

	// Convert to connect items
	resItems := []*itemv1.Item{}
	for _, item := range items {
		resItems = append(resItems, itemToConnect(item))
	}

	res := connect.NewResponse(&itemv1.GetItemsResponse{
		Items: resItems,
		Count: count,
	})
	return res, nil
}

func (h *Handler) CreateItem(ctx context.Context, req *connect.Request[itemv1.CreateItemRequest]) (*connect.Response[itemv1.CreateItemResponse], error) {
	userid, ok := interceptors.GetUserContext(ctx)
	if !ok {
		return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated"))
	}

	time := time.Now()

	// Insert item
	id, err := h.db.InsertItem(ctx, sqlc.InsertItemParams{
		Name:        req.Msg.Name,
		Added:       time,
		Description: req.Msg.Description,
		Price:       float64(req.Msg.Price),
		Quantity:    int64(req.Msg.Quantity),
		UserID:      userid,
	})
	if err != nil {
		return nil, connect.NewError(connect.CodeInternal, err)
	}

	res := connect.NewResponse(&itemv1.CreateItemResponse{
		Id:    id,
		Added: timestamppb.New(time),
	})
	return res, nil
}

func (h *Handler) UpdateItem(ctx context.Context, req *connect.Request[itemv1.UpdateItemRequest]) (*connect.Response[itemv1.UpdateItemResponse], error) {
	userid, ok := interceptors.GetUserContext(ctx)
	if !ok {
		return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated"))
	}

	// Update item
	err := h.db.UpdateItem(ctx, sqlc.UpdateItemParams{
		// set
		Name:        req.Msg.Name,
		Description: req.Msg.Description,
		Price:       util.NullFloat64(req.Msg.Price),
		Quantity:    util.NullInt64(req.Msg.Quantity),

		// where
		ID:     req.Msg.Id,
		UserID: userid,
	})
	if err != nil {
		return nil, connect.NewError(connect.CodeInternal, err)
	}

	res := connect.NewResponse(&itemv1.UpdateItemResponse{})
	return res, nil
}

func (h *Handler) DeleteItem(ctx context.Context, req *connect.Request[itemv1.DeleteItemRequest]) (*connect.Response[itemv1.DeleteItemResponse], error) {
	userid, ok := interceptors.GetUserContext(ctx)
	if !ok {
		return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("unauthenticated"))
	}

	// Delete item
	err := h.db.DeleteItem(ctx, sqlc.DeleteItemParams{
		ID:     req.Msg.Id,
		UserID: userid,
	})
	if err != nil {
		return nil, connect.NewError(connect.CodeInternal, err)
	}

	res := connect.NewResponse(&itemv1.DeleteItemResponse{})
	return res, nil
}

func NewHandler(vi *validate.Interceptor, db *sqlc.Queries, key string) (string, http.Handler) {
	interceptors := connect.WithInterceptors(vi, interceptors.NewAuthInterceptor(key))

	return itemv1connect.NewItemServiceHandler(
		&Handler{
			db:  db,
			key: []byte(key),
		},
		interceptors,
	)
}
