package controllers

import (
	"fmt"
	"log"
	"nearle/models"
	"net/http"
	"strconv"
	"strings"
	"time"

	"nearle/services"

	"github.com/gofiber/fiber/v2"
)

type OrderController struct {
	orderService services.OrderService
}

func NewOrderController(orderService services.OrderService) *OrderController {
	return &OrderController{orderService: orderService}
}

func (ctl *OrderController) GetOrders(c *fiber.Ctx) error {

    tid, _ := strconv.Atoi(c.Query("tenantid"))
    pid, _ := strconv.Atoi(c.Query("partnerid"))
    cid, _ := strconv.Atoi(c.Query("customerid"))
    mid, _ := strconv.Atoi(c.Query("moduleid"))
    aid, _ := strconv.Atoi(c.Query("applocationid"))
    uid, _ := strconv.Atoi(c.Query("appuserid"))
    lid, _ := strconv.Atoi(c.Query("locationid"))
    configid, _ := strconv.Atoi(c.Query("configid"))

    stat := c.Query("status")
    fdate := c.Query("fromdate")
    tdate := c.Query("todate")
    keyword := c.Query("keyword")

    pageno, _ := strconv.Atoi(c.Query("pageno"))
    pagesize, _ := strconv.Atoi(c.Query("pagesize"))

    if pageno <= 0 {
        pageno = 1
    }
    if pagesize <= 0 {
        pagesize = 10
    }

    // Build dynamic query struct
    query := models.DeliveryQuery{
        Tenantid:      tid,
        Partnerid:     pid,
        Customerid:    cid,
        Moduleid:      mid,
        Applocationid: aid,
        Locationid:    lid,
        UserID:        uid,
        Appuserid:     uid,
        Configid:      configid,
        Fromdate:      fdate,
        ToDate:        tdate,
        Status:        stat,
        Keyword:       keyword,
        Pageno:        pageno,
        Pagesize:      pagesize,
    }

    var (
        orders []models.OrderInfo
        err    error
    )

    // --------------------------
    // 🔥 DYNAMIC ROUTING LOGIC
    // --------------------------

    if tid != 0 && lid != 0 {
        // ⭐ Both tenant & location → special handler
        orders, err = ctl.orderService.GetTenantLocationOrders(query)

    } else if tid != 0 {
        // Tenant only
        orders, err = ctl.orderService.GetTenantOrders(query)

    } else if pid != 0 {
        // Partner
        orders, err = ctl.orderService.GetPartnerOrders(stat, fdate, tdate, pid, pageno, pagesize, keyword)

    } else if cid != 0 {
        // Customer
        orders, err = ctl.orderService.GetCustomerOrders(stat, fdate, tdate, cid, mid, pageno, pagesize, keyword)

    } else if aid != 0 {
        // App-location orders
        orders, err = ctl.orderService.GetAdminOrders(stat, fdate, tdate, aid, pageno, pagesize, keyword)

    } else if uid != 0 {
        // User orders
        orders, err = ctl.orderService.GetUserOrders(stat, fdate, tdate, uid, pageno, pagesize, keyword)

    } else {
        // No scoping id supplied (tenantid/partnerid/customerid/applocationid/appuserid).
        // Refuse instead of silently returning every order in the database.
        return c.Status(http.StatusBadRequest).JSON(fiber.Map{
            "status":  false,
            "code":    http.StatusBadRequest,
            "message": "At least one of tenantid, partnerid, customerid, applocationid or appuserid is required",
        })
    }

    if err != nil {
        return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
            "status":  false,
            "code":    http.StatusInternalServerError,
            "message": err.Error(),
        })
    }

    return c.JSON(fiber.Map{
        "status":  true,
        "code":    http.StatusOK,
        "message": "Success",
        "details": orders,
    })
}


func (ctl *OrderController) GetOrderSummary(c *fiber.Ctx) error {
	tid, _ := strconv.Atoi(c.Query("tenantid"))
	pid, _ := strconv.Atoi(c.Query("partnerid"))
	cid, _ := strconv.Atoi(c.Query("customerid"))
	lid, _ := strconv.Atoi(c.Query("locationid"))
	fdate := c.Query("fromdate")
	tdate := c.Query("todate")

	if tid == 0 && pid == 0 && cid == 0 && lid == 0 {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "At least one of tenantid, partnerid, customerid or locationid is required",
			"status":  false,
		})
	}

	data, err := ctl.orderService.GetOrderSummary(tid, pid, cid, lid, fdate, tdate)
	if err != nil {
		return c.Status(http.StatusConflict).JSON(fiber.Map{
			"code":    http.StatusConflict,
			"message": err.Error(),
			"status":  false,
		})
	}

	return c.Status(http.StatusOK).JSON(fiber.Map{
		"code":    http.StatusOK,
		"message": "Success",
		"status":  true,
		"details": data,
	})
}


func (ctl *OrderController) GetlocationOrderSummary(c *fiber.Ctx) error {
	tenantIDStr := c.Query("tenantid")
	tenantID, _ := strconv.Atoi(tenantIDStr)

	if tenantID == 0 {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"status":  false,
			"code":    http.StatusBadRequest,
			"message": "tenantid is required",
		})
	}

	data, err := ctl.orderService.GetLocationOrderSummary(tenantID)
	if err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
			"status":  false,
			"code":    http.StatusInternalServerError,
			"message": err.Error(),
		})
	}

	return c.Status(http.StatusOK).JSON(fiber.Map{
		"code":    http.StatusOK,
		"message": "Success",
		"status":  true,
		"details": data,
	})
}

func (ctl *OrderController) GetOrderInsights(c *fiber.Ctx) error {
	tenantIDStr := c.Query("tenantid")
	tenantID, _ := strconv.Atoi(tenantIDStr)

	insights, err := ctl.orderService.GetOrderInsights(tenantID)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"status":  false,
			"message": "Failed to fetch order insights",
			"error":   err.Error(),
		})
	}

	return c.Status(fiber.StatusOK).JSON(fiber.Map{
		"status":  true,
		"message": "Success",
		"details": insights,
	})
}

func (ctl *OrderController) GetOrderDetails(c *fiber.Ctx) error {
	orderHeaderIDStr := c.Query("orderheaderid")
	if orderHeaderIDStr == "" {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"code":    400,
			"message": "orderheaderid is required",
			"status":  false,
			"details": []interface{}{},
		})
	}

	orderHeaderID, err := strconv.Atoi(orderHeaderIDStr)
	if err != nil {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"code":    400,
			"message": "invalid orderheaderid",
			"status":  false,
			"details": []interface{}{},
		})
	}

	details, err := ctl.orderService.GetOrderDetails(orderHeaderID)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"code":    500,
			"message": "Failed to fetch order details",
			"status":  false,
			"details": []interface{}{},
		})
	}

	var orderAmount, totalTaxAmount float64
	if len(details) > 0 {
		orderAmount = details[0].Orderamount
		totalTaxAmount = details[0].Totaltaxamount
	}

	return c.JSON(fiber.Map{
		"code": 200,
		"pricedetails": fiber.Map{
			"orderamount":    orderAmount,
			"totaltaxamount": totalTaxAmount,
		},
		"details": details,
		"message": "Success",
		"status":  true,
	})
}

func (ctl *OrderController) UpdateOrder(c *fiber.Ctx) error {
	var order models.Orders

	if err := c.BodyParser(&order); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "Invalid request body",
			"status":  false,
		})
	}

	if err := ctl.orderService.UpdateOrder(&order); err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
			"code":    http.StatusInternalServerError,
			"message": "Error updating order",
			"status":  false,
		})
	}

	return c.Status(http.StatusAccepted).JSON(fiber.Map{
		"code":    http.StatusAccepted,
		"message": "Success",
		"status":  true,
	})
}

func (ctl *OrderController) CreateOrderv3(c *fiber.Ctx) error {
	var data models.Orders

	// 🛠️ Strategy 1: Try parsing direct object (for Apidog)
	if err := c.BodyParser(&data); err != nil {
		log.Println("BodyParser strategy 1 error:", err)
	}

	// 🛠️ Strategy 2: If Strategy 1 didn't find a Tenantid, try parsing wrapped object (for Mobile App)
	if data.Tenantid == 0 {
		type OrderWrapper struct {
			Orders models.Orders `json:"orders"`
		}
		var wrapper OrderWrapper
		if err := c.BodyParser(&wrapper); err == nil && wrapper.Orders.Tenantid != 0 {
			data = wrapper.Orders
		}
	}

	// 🛠️ Strategy 3: some clients send the header under "orders" but the line
	// items as a SIBLING top-level "items" array rather than nested inside
	// it. Strategy 2's OrderWrapper only has an "orders" field, so
	// encoding/json silently drops that sibling key — the order header
	// parses fine but data.Items ends up empty, which used to let the order
	// go through with zero items and skip the stock check entirely (the
	// pre-validation loop below iterates over data.Items). Pick it up here
	// if strategies 1/2 left Items empty.
	if len(data.Items) == 0 {
		type ItemsWrapper struct {
			Items []models.OrderDetail `json:"items"`
		}
		var itemsWrapper ItemsWrapper
		if err := c.BodyParser(&itemsWrapper); err == nil && len(itemsWrapper.Items) > 0 {
			data.Items = itemsWrapper.Items
		}
	}

	// Double check we have the required ID
	if data.Tenantid == 0 {
		return c.Status(http.StatusConflict).JSON(fiber.Map{
			"code":    http.StatusConflict,
			"message": "Tenant ID is required",
			"status":  false,
		})
	}

	// An order with no line items has nothing to check stock against — the
	// pre-validation loop in CreateOrder simply wouldn't run, silently
	// creating a phantom header-only order that never deducted stock.
	// Reject it outright instead.
	if len(data.Items) == 0 {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "Order must contain at least one item",
			"status":  false,
		})
	}

	if strings.TrimSpace(data.Orderdate) == "" {
		data.Orderdate = time.Now().Format("2006-01-02 15:04:05")
	}
	if strings.TrimSpace(data.Deliverytime) == "" {
		data.Deliverytime = time.Now().Format("2006-01-02 15:04:05")
	}

	order, err := ctl.orderService.CreateOrder(data)
	if err != nil {
		log.Println("CreateOrder service error:", err)
		statusCode := http.StatusInternalServerError
		if strings.Contains(strings.ToLower(err.Error()), "insufficient stock") {
			statusCode = http.StatusConflict
		}
		return c.Status(statusCode).JSON(fiber.Map{
			"code":    statusCode,
			"message": err.Error(),
			"status":  false,
		})
	}

	return c.Status(http.StatusOK).JSON(fiber.Map{
		"code":    http.StatusOK,
		"message": "Order created successfully",
		"status":  true,
		"details": order,
	})
}

// UploadOfflineSales imports a spreadsheet of in-store counter sales.
//
// The response is 200 whenever the batch was processed, even if individual
// bills were rejected, because a partial import is a normal outcome for a
// spreadsheet and the per-bill results carry the detail. A non-200 means
// nothing at all was attempted — a malformed body, or an outlet the caller has
// no claim on.
func (ctl *OrderController) UploadOfflineSales(c *fiber.Ctx) error {
	var input models.OfflineSalesUpload

	if err := c.BodyParser(&input); err != nil {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "could not read the upload: " + err.Error(),
			"status":  false,
		})
	}

	// locationid is optional: 0 means the bills carry their own branch, which
	// is how one workbook covers every outlet a merchant runs. Supplying it
	// pins the upload to that branch and rejects anything else in the file.
	if input.Tenantid <= 0 {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "tenantid is required",
			"status":  false,
		})
	}

	if len(input.Bills) == 0 {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "no sales rows found in the upload",
			"status":  false,
		})
	}

	result, err := ctl.orderService.UploadOfflineSales(input)
	if err != nil {
		log.Println("UploadOfflineSales service error:", err)
		// An outlet the caller doesn't own is a permission problem, not a
		// server fault, and is reported as one so the UI can say so plainly.
		statusCode := http.StatusInternalServerError
		if strings.Contains(strings.ToLower(err.Error()), "does not belong to tenant") {
			statusCode = http.StatusForbidden
		}
		return c.Status(statusCode).JSON(fiber.Map{
			"code":    statusCode,
			"message": err.Error(),
			"status":  false,
		})
	}

	message := fmt.Sprintf("%d bill(s) imported", result.Imported)
	if result.Duplicate > 0 {
		message += fmt.Sprintf(", %d already imported", result.Duplicate)
	}
	if result.Failed > 0 {
		message += fmt.Sprintf(", %d failed", result.Failed)
	}

	return c.Status(http.StatusOK).JSON(fiber.Map{
		"code":    http.StatusOK,
		"message": message,
		"status":  true,
		"details": result,
	})
}

func (ctl *OrderController) GetCustomerOrders(c *fiber.Ctx) error {
	customerID := c.Query("customerid")
	tenantID := c.Query("tenantid")
	moduleID := c.Query("moduleid")
	fromDate := c.Query("fromdate")
	toDate := c.Query("todate")
	orderStatus := c.Query("orderstatus")
	keyword := c.Query("keyword")

	pageNo, _ := strconv.Atoi(c.Query("pageno", "1"))
	pageSize, _ := strconv.Atoi(c.Query("pagesize", "10"))

	if pageNo < 1 {
		pageNo = 1
	}
	if pageSize < 1 {
		pageSize = 10
	}
	offset := (pageNo - 1) * pageSize

	if customerID == "" {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"status":  false,
			"message": "customerid is required",
		})
	}

	orders, err := ctl.orderService.GetCustomerOrdersv3(customerID, tenantID, moduleID, fromDate, toDate, orderStatus, keyword, pageSize, offset)
	if err != nil {
		log.Println("GetCustomerOrders error:", err)
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
			"code":    http.StatusInternalServerError,
			"status":  false,
			"message": "Failed to fetch customer orders",
			"error":   err.Error(),
		})
	}

	return c.JSON(fiber.Map{
		"code":    http.StatusOK,
		"status":  true,
		"message": "Customer orders fetched successfully",
		"data":    orders,
	})
}

func (ctl *OrderController) GetRevenueSummary(c *fiber.Ctx) error {
	tid, _ := strconv.Atoi(c.Query("tenantid"))
	lid, _ := strconv.Atoi(c.Query("locationid"))
	fdate := c.Query("fromdate")
	tdate := c.Query("todate")

	if tid == 0 && lid == 0 {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "Either tenantid or locationid query parameter is required",
			"status":  false,
		})
	}

	data, err := ctl.orderService.GetRevenueSummary(tid, lid, fdate, tdate)
	if err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
			"code":    http.StatusInternalServerError,
			"message": err.Error(),
			"status":  false,
		})
	}
	return c.Status(http.StatusOK).JSON(fiber.Map{
		"code":    http.StatusOK,
		"message": "Success",
		"status":  true,
		"details": data,
	})
}

func (ctl *OrderController) GetSalesSummary(c *fiber.Ctx) error {
	tid, _ := strconv.Atoi(c.Query("tenantid"))
	lid, _ := strconv.Atoi(c.Query("locationid"))
	fdate := c.Query("fromdate")
	tdate := c.Query("todate")

	if tid == 0 {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "tenantid query parameter is required",
			"status":  false,
		})
	}

	data, err := ctl.orderService.GetSalesSummary(tid, lid, fdate, tdate)
	if err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
			"code":    http.StatusInternalServerError,
			"message": err.Error(),
			"status":  false,
		})
	}

	return c.Status(http.StatusOK).JSON(fiber.Map{
		"code":    http.StatusOK,
		"message": "Success",
		"status":  true,
		"details": data,
	})
}

func (ctl *OrderController) GetTimeSeries(c *fiber.Ctx) error {
	tid, _ := strconv.Atoi(c.Query("tenantid"))
	lid, _ := strconv.Atoi(c.Query("locationid"))
	granularity := c.Query("granularity")
	fdate := c.Query("fromdate")
	tdate := c.Query("todate")

	if tid == 0 {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "tenantid query parameter is required",
			"status":  false,
		})
	}
	
	if granularity == "" {
		return c.Status(http.StatusBadRequest).JSON(fiber.Map{
			"code":    http.StatusBadRequest,
			"message": "granularity query parameter is required (day, month, year)",
			"status":  false,
		})
	}

	data, err := ctl.orderService.GetTimeSeries(tid, lid, granularity, fdate, tdate)
	if err != nil {
		return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
			"code":    http.StatusInternalServerError,
			"message": err.Error(),
			"status":  false,
		})
	}

	return c.Status(http.StatusOK).JSON(fiber.Map{
		"code":    http.StatusOK,
		"message": "Success",
		"status":  true,
		"details": data,
	})
}
