package controllers

import (
	"encoding/json"
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"

	"doormile/constants"
	"doormile/db"
	"doormile/internal/notify"
	"doormile/models"
	"doormile/utils"

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

// --------------------
// DUTY MANAGEMENT
// --------------------

func MilerStartDuty(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var req struct {
		Lat float64 `json:"lat"`
		Lon float64 `json:"lon"`
	}
	if err := c.BodyParser(&req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	midnight := todayMidnight()
	var existing models.MilerDutyLog
	if err := db.DB.Where("userid = ? AND onduty = ? AND logoutat IS NULL AND loginat >= ?", milerUserID, true, midnight).
		First(&existing).Error; err == nil {
		return utils.BadRequest(c, "already on duty, end current duty first")
	}

	now := time.Now()
	dutyLog := models.MilerDutyLog{
		Userid:   milerUserID,
		Loginat:  now,
		Onduty:   true,
		Startlat: req.Lat,
		Startlon: req.Lon,
		Lastlat:  req.Lat,
		Lastlon:  req.Lon,
	}
	if err := db.DB.Create(&dutyLog).Error; err != nil {
		return utils.Internal(c, "failed to start duty")
	}

	db.DB.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).Updates(map[string]interface{}{
		"availabilitystatus":    constants.MilerAvailable,
		"currentlatitude":       req.Lat,
		"currentlongitude":      req.Lon,
		"lastlocationupdatedat": now,
	})
	db.DB.Model(&models.AppUser{}).Where("userid = ?", milerUserID).Update("onduty", 1)

	return utils.OK(c, fiber.Map{
		"dutylogid": dutyLog.Dutylogid,
		"loginat":   dutyLog.Loginat,
	})
}

func MilerEndDuty(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var req struct {
		Lat float64 `json:"lat"`
		Lon float64 `json:"lon"`
	}
	if err := c.BodyParser(&req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}

	var dutyLog models.MilerDutyLog
	if err := db.DB.Where("userid = ? AND onduty = ? AND logoutat IS NULL", milerUserID, true).
		Order("loginat DESC").First(&dutyLog).Error; err != nil {
		return utils.BadRequest(c, "not currently on duty")
	}

	var activeAssignments int64
	db.DB.Model(&models.BookingAssignment{}).
		Where("mileruserid = ? AND assignmentstatus IN ?", milerUserID, []string{constants.AssignmentAssigned, constants.AssignmentAccepted}).
		Count(&activeAssignments)
	if activeAssignments > 0 {
		return utils.BadRequest(c, "cannot go off duty with active assignments")
	}

	now := time.Now()
	dutyLog.Logoutat = &now
	dutyLog.Onduty = false
	dutyLog.Lastlat = req.Lat
	dutyLog.Lastlon = req.Lon
	if err := db.DB.Save(&dutyLog).Error; err != nil {
		return utils.Internal(c, "failed to end duty")
	}

	db.DB.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).Update("availabilitystatus", constants.MilerOffline)
	db.DB.Model(&models.AppUser{}).Where("userid = ?", milerUserID).Update("onduty", 0)

	return utils.OK(c, fiber.Map{
		"dutylogid": dutyLog.Dutylogid,
		"logoutat":  dutyLog.Logoutat,
		"totalkms":  dutyLog.Totalkms,
	})
}

func MilerGetDutyStatus(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	midnight := todayMidnight()

	var dutyLog models.MilerDutyLog
	if err := db.DB.Where("userid = ? AND onduty = ? AND logoutat IS NULL AND loginat >= ?", milerUserID, true, midnight).
		Order("loginat DESC").First(&dutyLog).Error; err != nil {
		return utils.OK(c, fiber.Map{"onduty": false})
	}

	var profile models.MilerProfile
	db.DB.Where("userid = ?", milerUserID).First(&profile)

	return utils.OK(c, fiber.Map{
		"onduty":             true,
		"dutylogid":          dutyLog.Dutylogid,
		"loginat":            dutyLog.Loginat,
		"totalkms":           dutyLog.Totalkms,
		"availabilitystatus": profile.Availabilitystatus,
	})
}

// --------------------
// BREAK MANAGEMENT
// --------------------

func MilerStartBreak(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var req struct {
		Breaktype string `json:"breaktype"`
	}
	if err := c.BodyParser(&req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}
	if req.Breaktype == "" {
		req.Breaktype = "Personal"
	}

	var dutyLog models.MilerDutyLog
	if err := db.DB.Where("userid = ? AND onduty = ? AND logoutat IS NULL", milerUserID, true).
		Order("loginat DESC").First(&dutyLog).Error; err != nil {
		return utils.BadRequest(c, "not on duty")
	}

	var openBreak models.MilerBreakLog
	if err := db.DB.Where("userid = ? AND endat IS NULL", milerUserID).First(&openBreak).Error; err == nil {
		return utils.BadRequest(c, "break already active")
	}

	breakLog := models.MilerBreakLog{
		Userid:    milerUserID,
		Dutylogid: dutyLog.Dutylogid,
		Breaktype: req.Breaktype,
		Startat:   time.Now(),
	}
	if err := db.DB.Create(&breakLog).Error; err != nil {
		return utils.Internal(c, "failed to start break")
	}

	db.DB.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).Update("availabilitystatus", constants.MilerBreak)

	return utils.OK(c, fiber.Map{
		"breaklogid": breakLog.Breaklogid,
		"startat":    breakLog.Startat,
	})
}

func MilerEndBreak(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var breakLog models.MilerBreakLog
	if err := db.DB.Where("userid = ? AND endat IS NULL", milerUserID).
		Order("startat DESC").First(&breakLog).Error; err != nil {
		return utils.BadRequest(c, "no active break")
	}

	now := time.Now()
	breakLog.Endat = &now
	if err := db.DB.Save(&breakLog).Error; err != nil {
		return utils.Internal(c, "failed to end break")
	}

	db.DB.Model(&models.MilerProfile{}).Where("userid = ?", milerUserID).Update("availabilitystatus", constants.MilerAvailable)

	return utils.OK(c, fiber.Map{
		"breaklogid": breakLog.Breaklogid,
		"endat":      breakLog.Endat,
	})
}

// --------------------
// MILER'S OWN BOOKINGS
// --------------------

func MilerGetMyBookings(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	status := c.Query("status")
	dateStr := c.Query("date")

	query := db.DB.Where("assignedmileruserid = ?", milerUserID)
	if status != "" && status != "All" {
		query = query.Where("status = ?", status)
	}
	if dateStr != "" {
		if date, err := time.Parse("2006-01-02", dateStr); err == nil {
			query = query.Where("createdat >= ? AND createdat < ?", date, date.Add(24*time.Hour))
		}
	}

	var bookings []models.PickupBooking
	if err := query.Preload("Parcels").Preload("ServiceOptions").Order("createdat DESC").Find(&bookings).Error; err != nil {
		return utils.Internal(c, "failed to fetch bookings")
	}

	response := make([]fiber.Map, 0, len(bookings))
	for _, b := range bookings {
		var customer models.AppCustomer
		db.DB.Where("appcustomerid = ?", b.Appcustomerid).First(&customer)

		response = append(response, fiber.Map{
			"bookingid":         b.Bookingid,
			"bookingreference":  b.Bookingno,
			"status":            b.Status,
			"pickupaddress":     b.Pickupaddress,
			"pickuplatitude":    b.Pickuplatitude,
			"pickuplongitude":   b.Pickuplongitude,
			"deliveryaddress":   b.Deliveryaddress,
			"deliverylatitude":  b.Deliverylatitude,
			"deliverylongitude": b.Deliverylongitude,
			"customername":      strings.TrimSpace(customer.Firstname + " " + customer.Lastname),
			"customerphone":     customer.Phone,
			"parcels":           b.Parcels,
			"serviceoptions":    b.ServiceOptions,
			"createdat":         b.Createdat,
		})
	}

	return utils.List(c, response, int64(len(response)))
}

// --------------------
// DELIVERY CONFIRMATION
// --------------------

func MilerDeliverConsignment(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	consignmentID, err := strconv.Atoi(c.Params("id"))
	if err != nil {
		return utils.BadRequest(c, "invalid consignment ID")
	}

	var req struct {
		Otp                  string  `json:"otp"`
		Deliveredtoname      string  `json:"deliveredtoname"`
		Photourl             string  `json:"photourl"`
		Receiversignatureurl string  `json:"receiversignatureurl"`
		Lat                  float64 `json:"lat"`
		Lon                  float64 `json:"lon"`
	}
	if err := c.BodyParser(&req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}
	if req.Otp == "" {
		return utils.BadRequest(c, "otp is required")
	}
	if req.Deliveredtoname == "" {
		return utils.BadRequest(c, "deliveredtoname is required")
	}

	var consignment models.Consignment
	if err := db.DB.First(&consignment, consignmentID).Error; err != nil {
		return utils.NotFound(c, "consignment not found")
	}

	var booking models.PickupBooking
	if err := db.DB.Where("consignmentid = ? AND assignedmileruserid = ?", consignment.Consignmentid, milerUserID).
		First(&booking).Error; err != nil {
		return utils.NotFound(c, "assigned consignment not found")
	}

	if consignment.Status != constants.ConsignmentOutForDelivery {
		return utils.BadRequest(c, "consignment is not out for delivery")
	}

	tx := db.DB.Begin()

	// OTP generation/storage is Phase 2 — no delivery_otp field exists yet on
	// consignments, so acceptance of a non-empty OTP is treated as verified.
	proof := models.DeliveryProof{
		Consignmentid:        consignment.Consignmentid,
		Deliveredat:          time.Now(),
		Deliveredtoname:      req.Deliveredtoname,
		Receiversignatureurl: req.Receiversignatureurl,
		Photourl:             req.Photourl,
		Otpverified:          true,
		Geolatitude:          req.Lat,
		Geolongitude:         req.Lon,
		Createdby:            milerUserID,
	}
	if err := tx.Create(&proof).Error; err != nil {
		tx.Rollback()
		return utils.Internal(c, "failed to record delivery proof")
	}

	consignment.Status = constants.ConsignmentDelivered
	consignment.Updatedat = time.Now()
	tx.Save(&consignment)

	tx.Model(&models.BookingAssignment{}).
		Where("bookingid = ? AND mileruserid = ?", booking.Bookingid, milerUserID).
		Updates(map[string]interface{}{
			"assignmentstatus": constants.AssignmentCompleted,
			"completedat":      time.Now(),
		})

	tx.Commit()

	if db.Js != nil {
		payload := map[string]interface{}{
			"bookingid":     booking.Bookingid,
			"outcome":       "on_time",
			"consignmentid": consignment.Consignmentid,
			"mileruserid":   milerUserID,
		}
		if data, err := json.Marshal(payload); err == nil {
			if _, err := db.Js.Publish("booking.outcome", data); err != nil {
				utils.Warn("MilerDeliverConsignment: NATS publish failed", "consignment_id", consignment.Consignmentid, "error", err)
			}
		}
	}

	var customer models.AppCustomer
	if err := db.DB.Where("appcustomerid = ?", booking.Appcustomerid).First(&customer).Error; err == nil && customer.Devicetoken != "" {
		if notifyErr := notify.SendToDevice(
			customer.Devicetoken,
			"Parcel Delivered",
			"Your parcel has been delivered!",
			map[string]string{
				"booking_id":     strconv.Itoa(booking.Bookingid),
				"consignment_id": strconv.Itoa(consignment.Consignmentid),
			},
		); notifyErr != nil {
			utils.Warn("FCM: failed to notify customer on delivery", "booking_id", booking.Bookingid, "error", notifyErr)
		}
	}

	return c.JSON(fiber.Map{
		"success": true,
		"message": "delivery confirmed",
		"data": fiber.Map{
			"proofid":     proof.Proofid,
			"deliveredat": proof.Deliveredat,
		},
	})
}

// --------------------
// EARNINGS
// --------------------

type milerEarningsSummary struct {
	CompletedStops int64   `gorm:"column:completed_stops"`
	TotalKms       float64 `gorm:"column:total_kms"`
	TotalEarnings  float64 `gorm:"column:total_earnings"`
	TotalBonus     int     `gorm:"column:total_bonus"`
}

func MilerGetEarnings(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	period := c.Query("period", "daily")

	var start, end time.Time
	now := time.Now()

	switch period {
	case "weekly":
		start = todayMidnight().Add(-6 * 24 * time.Hour)
		end = todayMidnight().Add(24 * time.Hour)
	case "monthly":
		start = time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
		end = start.AddDate(0, 1, 0)
	default:
		period = "daily"
		day := todayMidnight()
		if dateStr := c.Query("date"); dateStr != "" {
			if parsed, err := time.Parse("2006-01-02", dateStr); err == nil {
				day = parsed
			}
		}
		start = day
		end = day.Add(24 * time.Hour)
	}

	var summary milerEarningsSummary
	db.DB.Model(&models.BookingAssignment{}).
		Where("mileruserid = ? AND assignmentstatus = ? AND completedat >= ? AND completedat < ?",
			milerUserID, constants.AssignmentCompleted, start, end).
		Select("COUNT(*) as completed_stops, COALESCE(SUM(riderkms),0) as total_kms, COALESCE(SUM(ridercharges),0) as total_earnings, COALESCE(SUM(bonuspoints),0) as total_bonus").
		Scan(&summary)

	var breakdown []models.BookingAssignment
	db.DB.Where("mileruserid = ? AND assignmentstatus = ? AND completedat >= ? AND completedat < ?",
		milerUserID, constants.AssignmentCompleted, start, end).
		Order("completedat DESC").Find(&breakdown)

	breakdownResp := make([]fiber.Map, 0, len(breakdown))
	for _, a := range breakdown {
		breakdownResp = append(breakdownResp, fiber.Map{
			"bookingid":    a.Bookingid,
			"riderkms":     a.Riderkms,
			"ridercharges": a.Ridercharges,
			"bonuspoints":  a.Bonuspoints,
			"completedat":  a.Completedat,
		})
	}

	data := fiber.Map{
		"period":          period,
		"completed_stops": summary.CompletedStops,
		"total_kms":       summary.TotalKms,
		"total_earnings":  summary.TotalEarnings,
		"total_bonus":     summary.TotalBonus,
		"breakdown":       breakdownResp,
	}
	if period == "daily" {
		data["date"] = start.Format("2006-01-02")
	}

	return utils.OK(c, data)
}

// --------------------
// NOTIFICATIONS (synthetic — no dedicated table yet)
// --------------------

// MilerGetNotifications generates notifications from real assignment events,
// mirroring GetHubNotifications' synthetic-notification pattern.
func MilerGetNotifications(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)
	midnight := todayMidnight()
	last24h := time.Now().Add(-24 * time.Hour)

	var notifications []notificationEntry

	var assigned []models.BookingAssignment
	db.DB.Where("mileruserid = ? AND assignedat >= ?", milerUserID, midnight).Find(&assigned)
	for _, a := range assigned {
		notifications = append(notifications, notificationEntry{
			Title: fmt.Sprintf("New pickup assigned — Booking #%d", a.Bookingid),
			Type:  "assignment",
			Time:  a.Assignedat,
		})
	}

	var completed []models.BookingAssignment
	db.DB.Where("mileruserid = ? AND assignmentstatus = ? AND completedat >= ?", milerUserID, constants.AssignmentCompleted, midnight).Find(&completed)
	for _, a := range completed {
		t := a.Assignedat
		if a.Completedat != nil {
			t = *a.Completedat
		}
		notifications = append(notifications, notificationEntry{
			Title: fmt.Sprintf("Booking #%d completed", a.Bookingid),
			Type:  "completed",
			Time:  t,
		})
	}

	var rejected []models.BookingAssignment
	db.DB.Where("mileruserid = ? AND assignmentstatus = ? AND assignedat >= ?", milerUserID, constants.AssignmentRejected, last24h).Find(&rejected)
	for _, a := range rejected {
		notifications = append(notifications, notificationEntry{
			Title: fmt.Sprintf("Assignment rejected — Booking #%d", a.Bookingid),
			Type:  "rejected",
			Time:  a.Assignedat,
		})
	}

	sort.Slice(notifications, func(i, j int) bool { return notifications[i].Time.After(notifications[j].Time) })
	if len(notifications) > 20 {
		notifications = notifications[:20]
	}

	response := make([]fiber.Map, 0, len(notifications))
	for i, n := range notifications {
		response = append(response, fiber.Map{
			"id":    i + 1,
			"title": n.Title,
			"type":  n.Type,
			"time":  humanizeRelativeTime(n.Time),
			"read":  false,
		})
	}

	return utils.List(c, response, int64(len(response)))
}

// MilerMarkNotificationRead is a stub until a notifications table with
// read-state exists — always succeeds without persisting anything, matching
// MarkNotificationRead's hub-console equivalent.
func MilerMarkNotificationRead(c *fiber.Ctx) error {
	return c.JSON(fiber.Map{"success": true})
}

// --------------------
// SUPPORT
// --------------------

func MilerCreateTicket(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var req struct {
		Subject     string `json:"subject"`
		Description string `json:"description"`
	}
	if err := c.BodyParser(&req); err != nil {
		return utils.BadRequest(c, "invalid request body")
	}
	if req.Subject == "" || req.Description == "" {
		return utils.BadRequest(c, "subject and description are required")
	}

	ticket := models.MilerSupportTicket{
		Userid:      milerUserID,
		Subject:     req.Subject,
		Description: req.Description,
		Status:      "Open",
	}
	if err := db.DB.Create(&ticket).Error; err != nil {
		return utils.Internal(c, "failed to create support ticket")
	}

	return utils.Created(c, fiber.Map{
		"ticketid": ticket.Ticketid,
		"status":   ticket.Status,
	})
}

func MilerGetTickets(c *fiber.Ctx) error {
	milerUserID := c.Locals("userid").(int)

	var tickets []models.MilerSupportTicket
	if err := db.DB.Where("userid = ?", milerUserID).Order("createdat DESC").Find(&tickets).Error; err != nil {
		return utils.Internal(c, "failed to fetch support tickets")
	}

	return utils.List(c, tickets, int64(len(tickets)))
}
