package http

import (
	"fmt"
	"github.com/UpmeshLTD/urus-aio/infrastructure/logging"
	"io"
	"log"
	"net/http"
	"os"

	swaggerFiles "github.com/swaggo/files"
	ginSwagger "github.com/swaggo/gin-swagger"

	"github.com/UpmeshLTD/urus-aio/infrastructure/server/transport/http/gin/middleware"
	"github.com/UpmeshLTD/urus-aio/shipment/config"
	"github.com/UpmeshLTD/urus-aio/shipment/docs"
	shipmentLog "github.com/UpmeshLTD/urus-aio/shipment/log"
	"github.com/UpmeshLTD/urus-aio/shipment/service"
	"github.com/gin-gonic/gin"
	"go.opencensus.io/plugin/ochttp"
)

//go:generate mockery --name=Server --inpackage --case=snake
type Server interface {
	Serve() error
}

type serverImpl struct {
	server *http.Server
	svc    service.Service
	config config.Config
	logger shipmentLog.Logger
}

func NewServer(svc service.Service, config config.Config, logger shipmentLog.Logger) Server {
	i := &serverImpl{
		svc:    svc,
		config: config,
		logger: logger,
	}

	i.initServer()
	return i
}

func (i *serverImpl) Serve() error {
	log.Printf(" 🚀 Listening HTTP on port %d", i.config.GetHTTPConfig().Port)
	return i.server.ListenAndServe()
}

func (i *serverImpl) initServer() {
	if i.config.GetAppConfig().EnableLogGin {
		// Send gin logs to writer
		logWriter := logging.Writer()
		if logWriter != nil && *logWriter != nil {
			// write logs to both std log and custom log writer
			gin.DefaultWriter = io.MultiWriter(os.Stdout, *logWriter)
		}
	} else {
		gin.DefaultWriter = io.Discard
	}

	gin.SetMode(i.config.GetHTTPConfig().Mode)
	r := i.initRouter()

	srv := &http.Server{
		Addr: fmt.Sprintf(":%d", i.config.GetHTTPConfig().Port),
		Handler: &ochttp.Handler{
			Handler:          r,
			IsPublicEndpoint: true,
		},
	}
	i.server = srv
}

func (i *serverImpl) initRouter() *gin.Engine {
	r := gin.Default()
	r.Use(middleware.NewCORSMiddleware())

	i.registerRoutes(r)

	if i.config.IsDevelopment() {
		i.initSwagger()
		r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
	}

	return r
}
func (i *serverImpl) initSwagger() {
	docs.SwaggerInfo.Host = i.config.GetSwaggerConfig().Host
	docs.SwaggerInfo.BasePath = i.config.GetSwaggerConfig().BasePath
}

func (i *serverImpl) registerRoutes(r *gin.Engine) {
	r.GET("/", i.svc.NewGetInfo())
	r.GET("/health-check", i.svc.NewHealthCheck())
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, map[string]string{
			"version": "1",
		})
	})

	r.Use(func(c *gin.Context) {
		c.Set("Language", middleware.GetAcceptLanguage(c))
	})

	// Dev API for testing
	if i.config.IsDevelopment() {
		r.POST("/admin/shipments/_status", i.svc.DevUpdateShipment)
		r.StaticFile("/admin/mock/webhook.html", "mock/webhook.html")

		// Webhook - Kerry
		r.POST("/webhook/kerry-express-th/status", i.svc.NotifyStatusKerryExpressTH)
	}

	// Webhook
	r.POST("/webhook/flash-express/status", i.svc.NotifyStatusFlashExpressTH)
	r.POST("/webhook/flash-express/courier", i.svc.NotifyCourierFlashExpressTH)
	r.POST("/webhook/jt-express/status", i.svc.NotifyStatusJTExpressTH)
	r.POST("/webhook/jt-express-my/status", i.svc.NotifyStatusJTExpressMY)
	r.POST("/webhook/jt-express-sg/status", i.svc.NotifyStatusJTExpressSG)

	// Webhook - FlashExpressPH
	r.POST("/webhook/flash-express-ph/status", i.svc.NotifyStatusFlashExpressPH)
	r.POST("/webhook/flash-express-ph/courier", i.svc.NotifyCourierFlashExpressPH)

	// Seller APIs
	sellerRoutes := r.Group("")
	sellerMiddleware := middleware.NewSellerAuthMiddleware(i.config.GetAuthConfig().User.Host, i.config.GetAuthConfig().User.Port)
	sellerRoutes.Use(sellerMiddleware)

	// Pickup point
	sellerRoutes.GET("/pickup-points", i.svc.GetListOfPickupPoints)
	sellerRoutes.GET("/pickup-points/:id", i.svc.GetPickupPoint)
	sellerRoutes.POST("/pickup-points", i.svc.CreatePickupPoint)
	sellerRoutes.PUT("/pickup-points/:id", i.svc.UpdatePickupPoint)
	sellerRoutes.DELETE("/pickup-points/:id", i.svc.DeletePickupPoint)

	// Courier
	sellerRoutes.GET("/couriers", i.svc.GetCouriers)
	sellerRoutes.GET("/couriers/:id", i.svc.GetCourier)
	sellerRoutes.POST("/couriers", i.svc.CreateCourier)
	sellerRoutes.PUT("/couriers/:id", i.svc.UpdateCourier)
	sellerRoutes.DELETE("/couriers/:id", i.svc.DeleteCourier)
	sellerRoutes.POST("/couriers/:id/link-account", i.svc.GetLinkAccountURL)

	// Shipment
	sellerRoutes.POST("/shipments", i.svc.CreateShipment)
	sellerRoutes.GET("/shipments/:id", i.svc.GetShipment)
	sellerRoutes.GET("/shipments/:id/status", i.svc.GetShipmentStatus)
	sellerRoutes.GET("/shipments", i.svc.GetShipments)
	sellerRoutes.POST("/shipments/labels", i.svc.GetShipmentLabels)
	sellerRoutes.POST("/shipments/labels/pdf", i.svc.GenerateShipmentLabels)
	sellerRoutes.PUT("/shipments/:id", i.svc.UpdateShipment)
	sellerRoutes.POST("/shipments/:id/cancel", i.svc.CancelShipment)
	sellerRoutes.POST("/shipments/:id/notify-pickup", i.svc.NotifyStaffToPickupParcel)
	sellerRoutes.POST("/shipments/notify-pickup", i.svc.NotifyStaffToPickupParcelInBatch)
	sellerRoutes.POST("/shipments/:id/archive", i.svc.ArchiveShipment)
	sellerRoutes.GET("/shipments/archived", i.svc.GetArchivedShipments)
	sellerRoutes.POST("/shipments/re-arrange", i.svc.ReArrangeShipments)

	// Seller Courier
	sellerRoutes.GET("/seller-couriers", i.svc.GetSellerCouriers)
	sellerRoutes.POST("/seller-couriers", i.svc.CreateSellerCourier)
	sellerRoutes.PUT("/seller-couriers/:id", i.svc.UpdateSellerCourier)
	sellerRoutes.DELETE("/seller-couriers/:id", i.svc.DeleteSellerCourier)
	sellerRoutes.GET("/seller-couriers/:id/associate-status", i.svc.GetAccountAssociateStatus)
	sellerRoutes.PUT("/seller-couriers/:id/status", i.svc.UpdateSellerCourierStatus)

	// Delivery Plan
	sellerRoutes.GET("/delivery-plans", i.svc.GetDeliveryPlans)
	sellerRoutes.POST("/delivery-plans", i.svc.CreateDeliveryPlan)
	sellerRoutes.PUT("/delivery-plans", i.svc.UpdateDeliveryPlan)
	sellerRoutes.DELETE("/delivery-plans", i.svc.DeleteDeliveryPlan)

	// Business Profile
	sellerRoutes.GET("/business-profiles/:id", i.svc.GetBusinessProfile)

	// Buyer APIs
	buyerRoutes := r.Group("")
	buyerRoutes.Use(middleware.NewBuyerAuthMiddleware(i.config.GetAuthConfig().Buyer.Host, i.config.GetAuthConfig().Buyer.Port))
	buyerRoutes.POST("/shipments/tracking-urls", i.svc.GetTrackingUrls)
	buyerRoutes.POST("/shipments/status", i.svc.GetShipmentOrdersStatus)
}
