package messages

import (
	"errors"
	"fmt"
	"strconv"
	"time"

	"github.com/android-sms-gateway/client-go/smsgateway"
	"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
	"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/converters"
	"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/middlewares/userauth"
	"github.com/android-sms-gateway/server/internal/sms-gateway/models"
	"github.com/android-sms-gateway/server/internal/sms-gateway/modules/devices"
	"github.com/android-sms-gateway/server/internal/sms-gateway/modules/messages"
	"github.com/capcom6/go-helpers/slices"
	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
	"go.uber.org/fx"
	"go.uber.org/zap"
)

const (
	route3rdPartyGetMessage = "3rdparty.get.message"
)

type thirdPartyControllerParams struct {
	fx.In

	MessagesSvc *messages.Service
	DevicesSvc  *devices.Service

	Validator *validator.Validate
	Logger    *zap.Logger
}

type ThirdPartyController struct {
	base.Handler

	messagesSvc *messages.Service
	devicesSvc  *devices.Service
}

//	@Summary		Enqueue message
//	@Description	Enqueues a message for sending. If `deviceId` is set, the specified device is used; otherwise a random registered device is chosen.
//	@Security		ApiAuth
//	@Tags			User, Messages
//	@Accept			json
//	@Produce		json
//	@Param			skipPhoneValidation	query		bool							false	"Skip phone validation"
//	@Param			deviceActiveWithin	query		int								false	"Filter devices active within the specified number of hours"	default(0)	minimum(0)
//	@Param			request				body		smsgateway.Message				true	"Send message request"
//	@Success		202					{object}	smsgateway.GetMessageResponse	"Message enqueued"
//	@Failure		400					{object}	smsgateway.ErrorResponse		"Invalid request"
//	@Failure		401					{object}	smsgateway.ErrorResponse		"Unauthorized"
//	@Failure		409					{object}	smsgateway.ErrorResponse		"Message with such ID already exists"
//	@Failure		500					{object}	smsgateway.ErrorResponse		"Internal server error"
//	@Header			202					{string}	Location						"Get message state URL"
//	@Router			/3rdparty/v1/messages [post]
//
// Enqueue message
func (h *ThirdPartyController) post(user models.User, c *fiber.Ctx) error {
	var params thirdPartyPostQueryParams
	if err := h.QueryParserValidator(c, &params); err != nil {
		return err
	}

	var req smsgateway.Message
	if err := h.BodyParserValidator(c, &req); err != nil {
		return err
	}

	var device models.Device
	var err error
	var filters []devices.SelectFilter

	if params.DeviceActiveWithin > 0 {
		filters = append(filters, devices.ActiveWithin(time.Duration(params.DeviceActiveWithin)*time.Hour))
	}

	// Check if device_id is provided
	if req.DeviceID != "" {

		device, err = h.devicesSvc.Get(user.ID, append(filters, devices.WithID(req.DeviceID))...)
		if err != nil {
			if errors.Is(err, devices.ErrNotFound) {
				return fiber.NewError(fiber.StatusBadRequest, "No active device with such ID found")
			}
			h.Logger.Error("Failed to get device", zap.Error(err), zap.String("user_id", user.ID), zap.String("device_id", req.DeviceID))
			return fiber.NewError(fiber.StatusInternalServerError, "Can't select device. Please contact support")
		}
	} else {
		// Fallback to random selection
		devices, err := h.devicesSvc.Select(user.ID, filters...)
		if err != nil {
			h.Logger.Error("Failed to select devices", zap.Error(err), zap.String("user_id", user.ID))
			return fiber.NewError(fiber.StatusInternalServerError, "Can't select devices. Please contact support")
		}

		if len(devices) < 1 {
			return fiber.NewError(fiber.StatusBadRequest, "No active devices found")
		}

		device, err = slices.Random(devices)
		if err != nil {
			return fmt.Errorf("can't get random device: %w", err)
		}
	}

	var textContent *messages.TextMessageContent
	var dataContent *messages.DataMessageContent
	if text := req.GetTextMessage(); text != nil {
		textContent = &messages.TextMessageContent{
			Text: text.Text,
		}
	} else if data := req.GetDataMessage(); data != nil {
		dataContent = &messages.DataMessageContent{
			Data: data.Data,
			Port: data.Port,
		}
	} else {
		return fiber.NewError(fiber.StatusBadRequest, "No message content provided")
	}

	msg := messages.MessageIn{
		ID: req.ID,

		TextContent: textContent,
		DataContent: dataContent,

		PhoneNumbers: req.PhoneNumbers,
		IsEncrypted:  req.IsEncrypted,

		SimNumber:          req.SimNumber,
		WithDeliveryReport: req.WithDeliveryReport,
		TTL:                req.TTL,
		ValidUntil:         req.ValidUntil,
		Priority:           req.Priority,
	}
	state, err := h.messagesSvc.Enqueue(device, msg, messages.EnqueueOptions{SkipPhoneValidation: params.SkipPhoneValidation})
	if err != nil {
		var errValidation messages.ErrValidation
		if isBadRequest := errors.As(err, &errValidation); isBadRequest {
			return fiber.NewError(fiber.StatusBadRequest, errValidation.Error())
		}
		if isConflict := errors.Is(err, messages.ErrMessageAlreadyExists); isConflict {
			return fiber.NewError(fiber.StatusConflict, err.Error())
		}

		return fmt.Errorf("can't enqueue message: %w", err)
	}

	location, err := c.GetRouteURL(route3rdPartyGetMessage, fiber.Map{
		"id": state.ID,
	})
	if err != nil {
		h.Logger.Warn("Failed to get route URL", zap.String("route", route3rdPartyGetMessage), zap.Error(err))
	} else {
		c.Location(location)
	}

	return c.Status(fiber.StatusAccepted).
		JSON(smsgateway.GetMessageResponse{
			ID:          state.ID,
			DeviceID:    state.DeviceID,
			State:       smsgateway.ProcessingState(state.State),
			IsHashed:    state.IsHashed,
			IsEncrypted: state.IsEncrypted,
			Recipients:  state.Recipients,
			States:      state.States,
		})
}

//	@Summary		Get messages
//	@Description	Retrieves a list of messages with filtering and pagination
//	@Security		ApiAuth
//	@Tags			User, Messages
//	@Produce		json
//	@Param			from		query		string							false	"Start date in RFC3339 format"			Format(date-time)
//	@Param			to			query		string							false	"End date in RFC3339 format"			Format(date-time)
//	@Param			state		query		string							false	"Filter messages by processing state"	Enum(Pending, Processed, Sent, Delivered, Failed)
//	@Param			deviceId	query		string							false	"Filter by device ID"					min(21)		max(21)
//	@Param			limit		query		int								false	"Pagination limit"						default(50)	min(1)	max(100)
//	@Param			offset		query		int								false	"Pagination offset"						default(0)
//	@Success		200			{object}	smsgateway.GetMessagesResponse	"A list of messages"
//	@Failure		400			{object}	smsgateway.ErrorResponse		"Invalid request"
//	@Failure		401			{object}	smsgateway.ErrorResponse		"Unauthorized"
//	@Failure		500			{object}	smsgateway.ErrorResponse		"Internal server error"
//	@Router			/3rdparty/v1/messages [get]
//
// Get message history
func (h *ThirdPartyController) list(user models.User, c *fiber.Ctx) error {
	params := thirdPartyGetQueryParams{}
	if err := h.QueryParserValidator(c, &params); err != nil {
		return err
	}

	messages, total, err := h.messagesSvc.SelectStates(user, params.ToFilter(), params.ToOptions())
	if err != nil {
		h.Logger.Error("Failed to get message history", zap.Error(err), zap.String("user_id", user.ID))
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve message history")
	}

	c.Set("X-Total-Count", strconv.Itoa(int(total)))
	return c.JSON(
		slices.Map(messages, converters.MessageStateToDTO),
	)
}

//	@Summary		Get message state
//	@Description	Returns message state by ID
//	@Security		ApiAuth
//	@Tags			User, Messages
//	@Produce		json
//	@Param			id	path		string							true	"Message ID"
//	@Success		200	{object}	smsgateway.GetMessageResponse	"Message state"
//	@Failure		400	{object}	smsgateway.ErrorResponse		"Invalid request"
//	@Failure		401	{object}	smsgateway.ErrorResponse		"Unauthorized"
//	@Failure		500	{object}	smsgateway.ErrorResponse		"Internal server error"
//	@Router			/3rdparty/v1/messages/{id} [get]
//
// Get message state
func (h *ThirdPartyController) get(user models.User, c *fiber.Ctx) error {
	id := c.Params("id")

	state, err := h.messagesSvc.GetState(user, id)
	if err != nil {
		if errors.Is(err, messages.ErrMessageNotFound) {
			return fiber.NewError(fiber.StatusNotFound, err.Error())
		}

		return err
	}

	return c.JSON(converters.MessageStateToDTO(state))
}

//	@Summary		Request inbox messages export
//	@Description	Initiates process of inbox messages export via webhooks. For each message the `sms:received` webhook will be triggered. The webhooks will be triggered without specific order.
//	@Security		ApiAuth
//	@Tags			User, Messages
//	@Accept			json
//	@Produce		json
//	@Param			request	body		smsgateway.MessagesExportRequest	true	"Export inbox request"
//	@Success		202		{object}	object								"Inbox export request accepted"
//	@Failure		400		{object}	smsgateway.ErrorResponse			"Invalid request"
//	@Failure		401		{object}	smsgateway.ErrorResponse			"Unauthorized"
//	@Failure		500		{object}	smsgateway.ErrorResponse			"Internal server error"
//	@Router			/3rdparty/v1/messages/inbox/export [post]
//
// Export inbox
func (h *ThirdPartyController) postInboxExport(user models.User, c *fiber.Ctx) error {
	req := smsgateway.MessagesExportRequest{}
	if err := h.BodyParserValidator(c, &req); err != nil {
		return err
	}

	device, err := h.devicesSvc.Get(user.ID, devices.WithID(req.DeviceID))
	if err != nil {
		if errors.Is(err, devices.ErrNotFound) {
			return fiber.NewError(fiber.StatusBadRequest, "Invalid device ID")
		}

		return err
	}

	if err := h.messagesSvc.ExportInbox(device, req.Since, req.Until); err != nil {
		return err
	}

	return c.SendStatus(fiber.StatusAccepted)
}

func (h *ThirdPartyController) Register(router fiber.Router) {
	router.Get("", userauth.WithUser(h.list))
	router.Post("", userauth.WithUser(h.post))
	router.Get(":id", userauth.WithUser(h.get)).Name(route3rdPartyGetMessage)

	router.Post("inbox/export", userauth.WithUser(h.postInboxExport))
}

func NewThirdPartyController(params thirdPartyControllerParams) *ThirdPartyController {
	return &ThirdPartyController{
		Handler: base.Handler{
			Logger:    params.Logger.Named("messages"),
			Validator: params.Validator,
		},
		messagesSvc: params.MessagesSvc,
		devicesSvc:  params.DevicesSvc,
	}
}
