package webhook

import (
	"context"
	"errors"
	"gorm.io/gorm"
	"log"

	"github.com/UpmeshLTD/urus-aio/infrastructure/db/entities"
	"github.com/UpmeshLTD/urus-aio/shipment/domain/flash_express_pickup"
	"github.com/UpmeshLTD/urus-aio/shipment/domain/publisher"
	"github.com/UpmeshLTD/urus-aio/shipment/domain/shipment"
	"github.com/UpmeshLTD/urus-aio/shipment/external/shipment/factory"
)

type Service interface {
	FlashExpressUpdateCourier(ctx context.Context, params *entities.FlashExpressUpdateCourierWebhookParams) error
	UpdateShipmentStatus(ctx context.Context, shipmentEntity *entities.Shipment, shipmentStatusLog *entities.ShipmentStatusLog) error
}

type serviceImpl struct {
	repo                      Repository
	shipmentService           shipment.Service
	externalShipmentFactory   factory.IExternalShipmentFactory
	flashExpressPickupService flash_express_pickup.Service
	publisherService          publisher.Service
}

func NewService(
	repo Repository,
	shipmentService shipment.Service,
	flashExpressPickupService flash_express_pickup.Service,
	externalShipmentFactory factory.IExternalShipmentFactory,
	publisherService publisher.Service,
) Service {
	return &serviceImpl{
		repo:                      repo,
		shipmentService:           shipmentService,
		flashExpressPickupService: flashExpressPickupService,
		externalShipmentFactory:   externalShipmentFactory,
		publisherService:          publisherService,
	}
}

func (s *serviceImpl) FlashExpressUpdateCourier(ctx context.Context, params *entities.FlashExpressUpdateCourierWebhookParams) error {
	var flashExpressPickup *entities.FlashExpressPickup

	// Get CourierTrackingIDs
	flashExpressPickup, err := s.flashExpressPickupService.GetByTicketPickupID(ctx, params.TicketPickupID)
	if err != nil {
		return err
	}

	params.CourierTrackingIDs = flashExpressPickup.CourierTrackingIDs

	// Check duplicate update
	_, e := s.repo.GetFlashExpressCourierWebhookLog(ctx, &entities.FlashExpressUpdateCourierWebhookLog{
		TicketPickupID:   params.TicketPickupID,
		State:            params.State,
		CancelReasonCode: params.CancelReasonCode,
		StateDate:        params.StateDate,
	})

	// If duplicated, return nil (success case)
	if e == nil {
		return nil
	}

	// If the error is not record not found, return error
	if e != nil && !errors.Is(e, gorm.ErrRecordNotFound) {
		return e
	}

	// If record not found, continue to update status
	courierStatusLogs := make([]*entities.FlashExpressUpdateCourierWebhookLog, 0)
	for _, v := range params.CourierTrackingIDs {
		courierStatusLogs = append(courierStatusLogs, &entities.FlashExpressUpdateCourierWebhookLog{
			CourierTrackingID:     v,
			TicketPickupID:        params.TicketPickupID,
			CurrentTicketPickupID: params.CurrentTicketPickupID,
			State:                 params.State,
			StateText:             params.StateText,
			CancelReasonText:      params.CancelReasonText,
			CancelOperatorID:      params.CancelOperatorID,
			CancelReasonCode:      params.CancelReasonCode,
			StateDate:             params.StateDate,
		})
	}

	if err := s.repo.CreateFlashExpressCourierWebhookLogs(ctx, courierStatusLogs); err != nil {
		log.Println("[FlashExpressUpdateCourier] Can't insert flash express courier webhook log record")
		return err
	}

	// Update shipment record
	if err := s.shipmentService.UpdateBatchCancelReason(ctx, params.CourierTrackingIDs, &entities.Shipment{
		CancelReasonText:     params.CancelReasonText,
		CancelReasonCode:     params.CancelReasonCode,
		CourierWebhookStatus: params.State,
	}); err != nil {
		log.Println("[FlashExpressUpdateCourier] Can't update cancel reason")
		return err
	}

	return nil
}

func (s *serviceImpl) UpdateShipmentStatus(ctx context.Context, shipmentEntity *entities.Shipment, shipmentStatusLog *entities.ShipmentStatusLog) error {
	// If canceled then not receive new webhook anymore
	if shipmentEntity.Status == entities.ShipmentStatusCancelled {
		return nil
	}

	// Check duplicate status
	row, err := s.repo.GetWebhookStatusLog(ctx, &entities.ShipmentStatusLog{
		CourierTrackingID: shipmentStatusLog.CourierTrackingID,
		CourierID:         shipmentStatusLog.CourierID,
	})
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}
	if row != nil && row.CourierStatus == shipmentStatusLog.CourierStatus && row.StateDate.Equal(shipmentStatusLog.StateDate) {
		log.Println("[Webhook][UpdateShipmentStatus] Shipment status already exists", shipmentStatusLog.CourierTrackingID, shipmentStatusLog.StateText)
		return nil
	}

	// Delete shipment and update paid orders if the status is cancelled
	if isCancelled(shipmentStatusLog.CourierID, shipmentStatusLog.CourierStatus) {
		shipmentEntity.CancelReasonCode = "courier_cancel"
		shipmentEntity.CancelReasonText = "Courier cancel"
		if err := s.shipmentService.CancelArrangedShip(ctx, shipmentEntity); err != nil {
			log.Println("[UpdateShipmentStatus] Failed to CancelArrangedShip:", err)
			return err
		}
		return nil
	}

	// Insert shipment status logs
	if err := s.repo.CreateWebhookStatusLog(ctx, shipmentStatusLog); err != nil {
		return err
	}

	// Update shipment status if the mapping status exists
	if shipmentStatusLog.Status != "" {
		if err := s.shipmentService.UpdateStatus(ctx, shipmentEntity.ID.String(), &entities.Shipment{
			Status:        shipmentStatusLog.Status,
			CourierStatus: shipmentStatusLog.CourierStatus,
			StateDate:     shipmentStatusLog.StateDate,
		}); err != nil {
			return err
		}

		if err := s.publisherService.PublishShipmentStatusChangedMessage(&entities.Shipment{
			CartID:          shipmentEntity.CartID,
			ShippingGroupID: shipmentEntity.ShippingGroupID,
			Status:          shipmentStatusLog.Status,
			StateDate:       shipmentStatusLog.StateDate,
		}); err != nil {
			log.Println("[UpdateShipmentStatus] Failed to publish a message status changed", "shipmentId", shipmentEntity.ID.String())
		}
	}

	return nil
}

func isCancelled(courierID int64, code string) bool {
	if courierID == entities.CourierIdFlashExpressTH {
		return code == "9"
	}
	if courierID == entities.CourierIdFlashExpressPH {
		return code == "9"
	}
	return false
}
