package handlers

import (
	"encoding/json"
	"strconv"
	"time"

	"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
	"github.com/gofiber/fiber/v2"
)

type CreateTicketOutcomeReq struct {
	// TicketID int64 `json:"ticket_id" example:"1"`
	EventID  int64 `json:"event_id" example:"1"`
	OddID    int64 `json:"odd_id" example:"1"`
	MarketID int64 `json:"market_id" example:"1"`
	// HomeTeamName string    `json:"home_team_name" example:"Manchester"`
	// AwayTeamName string    `json:"away_team_name" example:"Liverpool"`
	// MarketName   string    `json:"market_name" example:"Fulltime Result"`
	// Odd          float32   `json:"odd" example:"1.5"`
	// OddName      string    `json:"odd_name" example:"1"`
	// Expires      time.Time `json:"expires" example:"2025-04-08T12:00:00Z"`
}

type CreateTicketReq struct {
	Outcomes []CreateTicketOutcomeReq `json:"outcomes"`
	Amount   float32                  `json:"amount" example:"100.0"`
}
type CreateTicketRes struct {
	FastCode      int64 `json:"fast_code" example:"1234"`
	CreatedNumber int64 `json:"created_number" example:"3"`
}
type TicketRes struct {
	ID        int64                  `json:"id" example:"1"`
	Outcomes  []domain.TicketOutcome `json:"outcomes"`
	Amount    float32                `json:"amount" example:"100.0"`
	TotalOdds float32                `json:"total_odds" example:"4.22"`
}

// CreateTicket godoc
// @Summary Create a temporary ticket
// @Description Creates a temporary ticket
// @Tags ticket
// @Accept json
// @Produce json
// @Param createTicket body CreateTicketReq true "Creates ticket"
// @Success 200 {object} CreateTicketRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /ticket [post]
func (h *Handler) CreateTicket(c *fiber.Ctx) error {
	var req CreateTicketReq
	if err := c.BodyParser(&req); err != nil {
		h.logger.Error("Failed to parse CreateTicket request", "error", err)
		return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
	}

	if valErrs, ok := h.validator.Validate(c, req); !ok {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
	}

	// TODO Validate Outcomes Here and make sure they didn't expire
	// Validation for creating tickets
	if len(req.Outcomes) > 30 {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Too many odds/outcomes selected", nil, nil)
	}

	if req.Amount > 100000 {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with an amount above 100,000 birr", nil, nil)
	}

	clientIP := c.IP()
	count, err := h.ticketSvc.CountTicketByIP(c.Context(), clientIP)
	if err != nil {
		return response.WriteJSON(c, fiber.StatusInternalServerError, "Error fetching user info", nil, nil)
	}

	if count > 50 {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Ticket Limit reached", nil, nil)
	}
	var outcomes []domain.CreateTicketOutcome = make([]domain.CreateTicketOutcome, 0, len(req.Outcomes))
	var totalOdds float32 = 1
	for _, outcome := range req.Outcomes {
		eventIDStr := strconv.FormatInt(outcome.EventID, 10)
		marketIDStr := strconv.FormatInt(outcome.MarketID, 10)
		oddIDStr := strconv.FormatInt(outcome.OddID, 10)
		event, err := h.eventSvc.GetUpcomingEventByID(c.Context(), eventIDStr)
		if err != nil {
			return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid event id", err, nil)
		}

		// Checking to make sure the event hasn't already started
		currentTime := time.Now()
		if event.StartTime.Before(currentTime) {
			return response.WriteJSON(c, fiber.StatusBadRequest, "The event has already expired", nil, nil)
		}

		odds, err := h.prematchSvc.GetRawOddsByMarketID(c.Context(), marketIDStr, eventIDStr)

		if err != nil {
			return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid market id", err, nil)
		}
		type rawOddType struct {
			ID       string
			Name     string
			Odds     string
			Header   string
			Handicap string
		}
		var selectedOdd rawOddType
		var isOddFound bool = false
		for _, raw := range odds.RawOdds {
			var rawOdd rawOddType
			rawBytes, err := json.Marshal(raw)
			err = json.Unmarshal(rawBytes, &rawOdd)
			if err != nil {
				h.logger.Error("Failed to unmarshal raw odd:", "error", err)
				continue
			}
			if rawOdd.ID == oddIDStr {
				selectedOdd = rawOdd
				isOddFound = true
			}
		}

		if !isOddFound {
			return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid odd id", nil, nil)
		}

		parsedOdd, err := strconv.ParseFloat(selectedOdd.Odds, 32)
		totalOdds = totalOdds * float32(parsedOdd)
		outcomes = append(outcomes, domain.CreateTicketOutcome{
			EventID:      outcome.EventID,
			OddID:        outcome.OddID,
			MarketID:     outcome.MarketID,
			HomeTeamName: event.HomeTeam,
			AwayTeamName: event.AwayTeam,
			MarketName:   odds.MarketName,
			Odd:          float32(parsedOdd),
			OddName:      selectedOdd.Name,
			OddHeader:    selectedOdd.Header,
			OddHandicap:  selectedOdd.Handicap,
			Expires:      event.StartTime,
		})

	}
	totalWinnings := req.Amount * totalOdds
	if totalWinnings > 1000000 {
		return response.WriteJSON(c, fiber.StatusBadRequest, "Cannot create a ticket with 1,000,000 winnings", nil, nil)
	}
	ticket, err := h.ticketSvc.CreateTicket(c.Context(), domain.CreateTicket{
		Amount:    domain.ToCurrency(req.Amount),
		TotalOdds: totalOdds,
		IP:        clientIP,
	})
	if err != nil {
		h.logger.Error("CreateTicketReq failed", "error", err)
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Internal server error",
		})
	}

	// Add the ticket id now that it has fetched from the database
	for index := range outcomes {
		outcomes[index].TicketID = ticket.ID
	}

	rows, err := h.ticketSvc.CreateTicketOutcome(c.Context(), outcomes)

	if err != nil {
		h.logger.Error("CreateTicketReq failed to create outcomes", "error", err)
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": "Internal server error",
		})
	}
	res := CreateTicketRes{
		FastCode:      ticket.ID,
		CreatedNumber: rows,
	}
	return response.WriteJSON(c, fiber.StatusOK, "Ticket Created", res, nil)

}

// GetTicketByID godoc
// @Summary Get ticket by ID
// @Description Retrieve ticket details by ticket ID
// @Tags ticket
// @Accept json
// @Produce json
// @Param id path int true "Ticket ID"
// @Success 200 {object} TicketRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /ticket/{id} [get]
func (h *Handler) GetTicketByID(c *fiber.Ctx) error {
	ticketID := c.Params("id")
	id, err := strconv.ParseInt(ticketID, 10, 64)
	if err != nil {
		h.logger.Error("Invalid ticket ID", "ticketID", ticketID, "error", err)
		return fiber.NewError(fiber.StatusBadRequest, "Invalid ticket ID")
	}

	ticket, err := h.ticketSvc.GetTicketByID(c.Context(), id)
	if err != nil {
		h.logger.Error("Failed to get ticket by ID", "ticketID", id, "error", err)
		return fiber.NewError(fiber.StatusNotFound, "Failed to retrieve ticket")
	}

	res := TicketRes{
		ID:        ticket.ID,
		Outcomes:  ticket.Outcomes,
		Amount:    ticket.Amount.Float32(),
		TotalOdds: ticket.TotalOdds,
	}
	return response.WriteJSON(c, fiber.StatusOK, "Ticket retrieved successfully", res, nil)
}

// GetAllTickets godoc
// @Summary Get all tickets
// @Description Retrieve all tickets
// @Tags ticket
// @Accept json
// @Produce json
// @Success 200 {array} TicketRes
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Router /ticket [get]
func (h *Handler) GetAllTickets(c *fiber.Ctx) error {

	tickets, err := h.ticketSvc.GetAllTickets(c.Context())
	if err != nil {
		h.logger.Error("Failed to get tickets", "error", err)
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve tickets")
	}

	res := make([]TicketRes, len(tickets))
	for i, ticket := range tickets {
		res[i] = TicketRes{
			ID:        ticket.ID,
			Outcomes:  ticket.Outcomes,
			Amount:    ticket.Amount.Float32(),
			TotalOdds: ticket.TotalOdds,
		}
	}

	return response.WriteJSON(c, fiber.StatusOK, "All tickets retrieved successfully", res, nil)
}
