package server

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"time"

	"github.com/a-h/templ"
	"github.com/labstack/echo/v4"
	"github.com/labstack/gommon/log"
	u "github.com/rjNemo/underscore"

	"github.com/rjNemo/rentease/internal/config"
	"github.com/rjNemo/rentease/internal/constant"
	"github.com/rjNemo/rentease/internal/service/booking"
	"github.com/rjNemo/rentease/internal/view"
	myTime "github.com/rjNemo/rentease/pkg/time"
)

func handleBookingListPage(bs *booking.Service, hc *config.Host) echo.HandlerFunc {
	return func(c echo.Context) error {
		search := c.FormValue("search")

		var bookings []*booking.Line
		if search != "" {
			bookings = bs.Search(search)
		} else {
			bookings = bs.All()
		}

		bvm := u.Map(bookings, func(b *booking.Line) *view.ListBookingsViewModel {
			return &view.ListBookingsViewModel{
				Id:       b.InvoiceNumber(hc),
				Url:      templ.SafeURL(fmt.Sprintf("%s/%d", constant.RouteBooking, b.Id)),
				From:     b.From.Format(time.DateOnly),
				To:       b.To.Format(time.DateOnly),
				Platform: b.Platform,
				Name:     b.CustomerName,
				Total:    strconv.FormatFloat(b.Total, 'f', 2, 64),
				Canceled: b.Canceled,
			}
		})

		if hxRequest(c) && !hxBoosted(c) {
			return renderTempl(c, http.StatusOK, view.BookingLines(bvm))
		} else {
			return renderTempl(c, http.StatusOK, view.ListBookings(bvm))
		}
	}
}

func handleBookingList(bs *booking.Service) echo.HandlerFunc {
	return func(c echo.Context) error {
		search := c.FormValue("search")

		var bookings []*booking.Line
		if search != "" {
			bookings = bs.Search(search)
		} else {
			bookings = bs.All()
		}

		return c.JSON(http.StatusOK, bookings)
	}
}

func handleBookingCreatePage(hc *config.Host) echo.HandlerFunc {
	return func(c echo.Context) error {
		return renderTempl(c, http.StatusOK, view.NewBooking(hc.Platforms))
	}
}

func handleBookingCreate(bs *booking.Service) echo.HandlerFunc {
	return func(c echo.Context) error {
		type NewBooking struct {
			From           time.Time `json:"from"`
			To             time.Time `json:"to"`
			ExternalId     *string   `form:"external_id"`
			Name           string    `form:"name"`
			PhoneNumber    string    `form:"phone_number"`
			Email          string    `form:"email"`
			Platform       string    `form:"platform"`
			CustomerNumber int       `form:"customer_number"`
			PlatformFees   float64   `form:"platform_fees"`
		}
		nb := new(NewBooking)
		err := c.Bind(nb)
		if err != nil {
			log.Warn(err)
			return err
		}

		ts, _ := myTime.ParseFromForm(c.FormValue("from"))
		nb.From = ts
		ts, _ = myTime.ParseFromForm(c.FormValue("to"))
		nb.To = ts

		if *nb.ExternalId == "" {
			nb.ExternalId = nil
		}
		b := bs.Create(nb.From, nb.To, nb.Name, nb.PhoneNumber, nb.Email, nb.Platform, nb.CustomerNumber, nb.PlatformFees, nb.ExternalId)
		return c.Redirect(http.StatusSeeOther, fmt.Sprintf("%s/%d", constant.RouteBooking, b.Id))
	}
}

func handleBookingPage(bs *booking.Service, hc *config.Host) echo.HandlerFunc {
	return func(c echo.Context) error {
		idStr := c.Param("id")
		id, err := strconv.Atoi(idStr)
		if err != nil {
			return err
		}

		b := bs.One(id)

		var eid string
		if b.ExternalId == nil {
			eid = ""
		} else {
			eid = *b.ExternalId
		}
		bvm := &view.BookingViewModel{
			Id:             b.InvoiceNumber(hc),
			Name:           b.Name,
			PhoneNumber:    b.PhoneNumber,
			CustomerNumber: strconv.Itoa(b.CustomerNumber),
			Email:          b.Email,
			From:           b.From.Format(time.DateOnly),
			To:             b.To.Format(time.DateOnly),
			Platform:       b.Platform,
			ExternalId:     eid,
			Canceled:       b.Canceled,
			PlatformFees:   strconv.FormatFloat(b.PlatformFees, 'f', 2, 64),
			Url:            templ.EscapeString(fmt.Sprintf("%s/%d", constant.RouteBooking, b.Id)),
			PdfUrl:         templ.SafeURL(fmt.Sprintf("%s/pdf/%d", constant.RouteBooking, b.Id)),
			CancelUrl:      fmt.Sprintf("%s/%d/cancel", constant.RouteBooking, b.Id),
			PaymentUrl:     fmt.Sprintf("%s/%d", constant.RoutePayment, b.Id),
			Items: view.ItemListViewModel{
				Items: u.Map(b.Items, func(i booking.Item) view.ItemViewModel {
					return view.ItemViewModel{
						Id:            strconv.Itoa(i.Id),
						Item:          i.Item,
						Quantity:      strconv.Itoa(i.Quantity),
						Price:         strconv.FormatFloat(i.Price, 'f', 2, 64),
						SubTotal:      strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
						PaymentStatus: i.PaymentStatus,
						ItemUrl:       fmt.Sprintf("%s/%d", constant.RouteItem, i.Id),
					}
				}),
				Payments: u.Map(b.Payments, func(p booking.Payment) view.PaymentViewModel {
					return view.PaymentViewModel{
						Amount:        strconv.FormatFloat(p.Amount, 'f', 2, 64),
						PaymentMethod: p.PaymentMethod,
						PaymentUrl:    fmt.Sprintf("%s/%d", constant.RoutePayment, p.ID),
					}
				}),
			},
			Total: strconv.FormatFloat(u.Reduce(b.Items, func(i booking.Item, sum float64) float64 {
				return sum + i.Price*float64(i.Quantity)
			}, 0.0), 'f', 2, 64),
			Platforms: hc.Platforms,
			ItemList: u.OrderBy(func(items map[string]config.HostItem) (out []string) { // TODO: return the full item to prefill the form
				for _, item := range items {
					out = append(out, item.Name)
				}
				return out
			}(hc.Items),
				func(l, r string) bool { return l > r },
			),
			PaymentMethods: hc.PaymentMethods,
		}
		return renderTempl(c, http.StatusOK, view.BookingById(bvm))
	}
}

func handleBookingUpdate(bs *booking.Service, hc *config.Host) echo.HandlerFunc {
	return func(c echo.Context) error {
		type UpdateBooking struct {
			From           time.Time `json:"from"`
			To             time.Time `json:"to"`
			ExternalId     *string   `form:"external_id"`
			Name           string    `form:"name"`
			PhoneNumber    string    `form:"phone_number"`
			Email          string    `form:"email"`
			Platform       string    `form:"platform"`
			Id             int       `param:"id"`
			CustomerNumber int       `form:"customer_number"`
			PlatformFees   float64   `form:"platform_fees"`
		}
		nb := new(UpdateBooking)
		err := c.Bind(nb)
		if err != nil {
			log.Warn(err)
			return err
		}

		ts, _ := myTime.ParseFromForm(c.FormValue("from"))
		nb.From = ts
		ts, _ = myTime.ParseFromForm(c.FormValue("to"))
		nb.To = ts

		if *nb.ExternalId == "" {
			nb.ExternalId = nil
		}

		b := bs.Update(nb.Id, nb.From, nb.To, nb.Name, nb.PhoneNumber, nb.Email, nb.Platform, nb.CustomerNumber, nb.PlatformFees, nb.ExternalId)

		form := view.BookingForm(view.BookingViewModel{
			Id:             b.InvoiceNumber(hc),
			Name:           b.Name,
			PhoneNumber:    b.PhoneNumber,
			CustomerNumber: strconv.Itoa(b.CustomerNumber),
			Email:          b.Email,
			From:           b.From.Format(time.DateOnly),
			To:             b.To.Format(time.DateOnly),
			Canceled:       b.Canceled,
			Platform:       b.Platform,
			ExternalId:     *b.ExternalId,
			Platforms:      hc.Platforms,
			PlatformFees:   strconv.FormatFloat(b.PlatformFees, 'f', 2, 64),
			PaymentMethods: hc.PaymentMethods,
			Url:            templ.EscapeString(fmt.Sprintf("%s/%d", constant.RouteBooking, b.Id)),
			PdfUrl:         templ.SafeURL(fmt.Sprintf("%s/pdf/%d", constant.RouteBooking, b.Id)),
		})

		return renderTempl(c, http.StatusOK, form)
	}
}

func handleLineItemForm(bs *booking.Service) echo.HandlerFunc {
	return func(c echo.Context) error {
		idStr := c.Param("id")
		id, err := strconv.Atoi(idStr)
		if err != nil {
			return err
		}
		i := bs.OneItem(id)
		form := view.LineItemForm(&view.ItemViewModel{
			Id:            strconv.Itoa(i.Id),
			Item:          i.Item,
			Quantity:      strconv.Itoa(i.Quantity),
			Price:         strconv.FormatFloat(i.Price, 'f', 2, 64),
			PaymentStatus: i.PaymentStatus,
			SubTotal:      strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
			ItemUrl:       fmt.Sprintf("%s/%d", constant.RouteItem, i.Id),
		})
		return renderTempl(c, http.StatusOK, form)
	}
}

func handleCreateItem(bs *booking.Service, hc *config.Host) echo.HandlerFunc {
	type NewItem struct {
		Item          string  `form:"item"`
		PaymentMethod string  `form:"method"`
		Quantity      int     `form:"quantity"`
		Price         float64 `form:"price"`
	}

	return func(c echo.Context) error {
		bookingIdStr := c.Param("id")
		bid, err := strconv.Atoi(bookingIdStr)
		if err != nil {
			return err
		}

		b := bs.One(bid)

		ni := new(NewItem)
		if err := c.Bind(ni); err != nil {
			log.Warn(err)
			return err
		}

		itm, ok := hc.Items[ni.Item]
		if !ok {
			return fmt.Errorf("invalid item name %q", ni.Item)
		}

		newItems := bs.CreateItem(b.Id, itm, ni.Quantity, ni.Price, ni.PaymentMethod, b.CustomerNumber, b.Platform)

		// TODO: fix the calendar integration
		// if err = cs.Create(
		// 	itm.CalendarId,
		// 	b.Name,
		// 	fmt.Sprintf("Reservation: %s\n %d voyageur(s)\n", b.Name, b.CustomerNumber),
		// 	b.From, b.To,
		// ); err != nil {
		// 	log.Warnf("could not create event: %s", err)
		// 	captureError(c, err)
		// }

		for _, i := range newItems {
			_ = renderTempl(c, http.StatusCreated, view.LineItem(&view.ItemViewModel{
				Id:            strconv.Itoa(i.Id),
				Item:          i.Item,
				Quantity:      strconv.Itoa(i.Quantity),
				Price:         strconv.FormatFloat(i.Price, 'f', 2, 64),
				PaymentStatus: i.PaymentStatus,
				SubTotal:      strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
				ItemUrl:       fmt.Sprintf("%s/%d", constant.RouteItem, i.Id),
			}))
		}

		return nil
	}
}

func handleItemPay(bs *booking.Service) echo.HandlerFunc {
	return func(c echo.Context) error {
		itemIdStr := c.Param("id")
		itemId, err := strconv.Atoi(itemIdStr)
		if err != nil {
			return err
		}

		i := bs.PayItem(itemId)
		return renderTempl(c, http.StatusOK, view.LineItem(&view.ItemViewModel{
			Id:            itemIdStr,
			Item:          i.Item,
			Quantity:      strconv.Itoa(i.Quantity),
			Price:         strconv.FormatFloat(i.Price, 'f', 2, 64),
			PaymentStatus: i.PaymentStatus,
			SubTotal:      strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
			ItemUrl:       fmt.Sprintf("%s/%d", constant.RouteItem, i.Id),
		}))
	}
}

func handleItemUpdate(bs *booking.Service) echo.HandlerFunc {
	return func(c echo.Context) error {
		type updateItem struct {
			Item          string  `form:"item"`
			PaymentMethod string  `form:"paymentMethod"`
			PaymentStatus string  `form:"paymentStatus"`
			Id            int     `param:"id"`
			Quantity      int     `form:"quantity"`
			Price         float64 `form:"price"`
		}
		ui := new(updateItem)

		if err := c.Bind(ui); err != nil {
			log.Warn(err)
			return err
		}

		i := bs.UpdateItem(ui.Id, ui.Item, ui.Quantity, ui.Price, ui.PaymentMethod, ui.PaymentStatus)

		return renderTempl(c, http.StatusCreated, view.LineItem(&view.ItemViewModel{
			Id:            strconv.Itoa(ui.Id),
			Item:          i.Item,
			Quantity:      strconv.Itoa(i.Quantity),
			Price:         strconv.FormatFloat(i.Price, 'f', 2, 64),
			PaymentStatus: i.PaymentStatus,
			SubTotal:      strconv.FormatFloat(i.Price*float64(i.Quantity), 'f', 2, 64),
			ItemUrl:       fmt.Sprintf("%s/%d", constant.RouteItem, i.Id),
		}))
	}
}

func handlePaymentUpdate(bs *booking.Service) echo.HandlerFunc {
	type updatePayment struct {
		Id            int     `param:"id"`
		Amount        float64 `form:"amount"`
		PaymentMethod string  `form:"paymentMethod"`
	}
	return func(c echo.Context) error {
		up := new(updatePayment)

		if err := c.Bind(up); err != nil {
			return fmt.Errorf("could not parse update payment request body: %w", err)
		}

		p := bs.UpdatePayment(up.Id, up.Amount, up.PaymentMethod)

		return renderTempl(c, http.StatusOK, view.PaymentLine(&view.PaymentViewModel{
			Amount:        strconv.FormatFloat(p.Amount, 'f', 2, 64),
			PaymentMethod: p.PaymentMethod,
			PaymentUrl:    fmt.Sprintf("%s/%d", constant.RoutePayment, p.ID),
		}))
	}
}

func handleBookingCancel(bs *booking.Service) echo.HandlerFunc {
	return func(c echo.Context) error {
		idStr := c.Param("id")
		id, err := strconv.Atoi(idStr)
		if err != nil {
			return err
		}

		bs.Cancel(id)

		return renderTempl(c, http.StatusOK, templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
			_, err := io.WriteString(w, " <span>Canceled</span>")
			return err
		}))
	}
}
