package controllers

import (
	"encoding/json"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"gitlab.com/pactual1/backend/database/contract"
	"gitlab.com/pactual1/backend/models"
	"gitlab.com/pactual1/backend/shared"
)

const (
	BlockchainSecretLength = 16
)

func GetLatestContracts(c *gin.Context) {
	// Existing parameters
	limitStr := c.DefaultQuery("limit", "50")
	offsetStr := c.DefaultQuery("offset", "0")
	status := strings.Split(c.DefaultQuery("status", models.ContractStatusActive), ",")

	// New/Updated optional parameters
	companyName := c.Query("company_name")
	companyAddress := c.Query("company_address")
	companyEmail := c.Query("company_email")
	companyPhone := c.Query("company_phone")
	contractName := c.Query("contract_name")
	startTimeStr := c.Query("start_time")
	endTimeStr := c.Query("end_time")
	deviceIDsStr := c.QueryArray("deviceIDs[]")
	iDsStr := c.QueryArray("ids[]")

	// Convert limit and offset to int
	limit, err := strconv.Atoi(limitStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid limit value"})
		return
	}

	offset, err := strconv.Atoi(offsetStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid offset value"})
		return
	}

	// Convert startTime to time.Time
	var startTime time.Time
	if startTimeStr != "" {
		startTimeUnix, err := strconv.ParseInt(startTimeStr, 10, 64)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid start_time value"})
			return
		}
		startTime = time.Unix(startTimeUnix, 0)
	}

	// Convert endTime to time.Time
	var endTime time.Time
	if endTimeStr != "" {
		endTimeUnix, err := strconv.ParseInt(endTimeStr, 10, 64)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid end_time value"})
			return
		}
		endTime = time.Unix(endTimeUnix, 0)
	}

	// Convert deviceIDs to []int64
	var deviceIDs []int64
	for _, idStr := range deviceIDsStr {
		id, err := strconv.ParseInt(idStr, 10, 64)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid deviceID value"})
			return
		}
		deviceIDs = append(deviceIDs, id)
	}

	// Convert ids to []int64
	var contractIDs []int64
	for _, idStr := range iDsStr {
		id, err := strconv.ParseInt(idStr, 10, 64)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid id value"})
			return
		}
		contractIDs = append(contractIDs, id)
	}

	// Fetch contracts
	contracts, total, st, err := contract.GetContracts(status, companyName, companyAddress, companyEmail, companyPhone, &startTime, &endTime, contractName, deviceIDs, contractIDs, nil, limit, offset)

	if err != nil {
		c.JSON(st, gin.H{"error": err.Error()})
		return
	}

	// Respond with the contracts and the total count
	c.JSON(http.StatusOK, gin.H{"total": total, "data": models.ConvertContractToDashboardResponse(contracts)})
}

func GetBuyerContracts(c *gin.Context) {
	// Existing parameters
	limitStr := c.DefaultQuery("limit", "50")
	offsetStr := c.DefaultQuery("offset", "0")
	status := c.QueryArray("status")
	iDsStr := c.QueryArray("ids[]")
	qStr := c.Query("q")
	dateCreatedStr := c.Query("date_created")
	startTimeStr := c.Query("start_time")
	endTimeStr := c.Query("end_time")

	// Convert limit and offset to int
	limit, err := strconv.Atoi(limitStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid limit value"})
		return
	}

	offset, err := strconv.Atoi(offsetStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid offset value"})
		return
	}

	// Convert startTime to time.Time
	var startTime *time.Time
	if startTimeStr != "" {
		startTimeUnix, err := strconv.ParseInt(startTimeStr, 10, 64)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid start_time value"})
			return
		}
		startTimeValue := time.Unix(startTimeUnix, 0)
		startTime = &startTimeValue
	}

	// Convert endTime to time.Time
	var endTime *time.Time
	if endTimeStr != "" {
		endTimeUnix, err := strconv.ParseInt(endTimeStr, 10, 64)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid end_time value"})
			return
		}
		endTimeValue := time.Unix(endTimeUnix, 0)
		endTime = &endTimeValue
	}

	// Convert startTime to time.Time
	var dateCreated time.Time
	if dateCreatedStr != "" {
		startTimeUnix, err := strconv.ParseInt(dateCreatedStr, 10, 64)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid start_time value"})
			return
		}
		dateCreated = time.Unix(startTimeUnix, 0)
	}

	// Convert ids to []int64
	var contractIDs []int64
	for _, idStr := range iDsStr {
		id, err := strconv.ParseInt(idStr, 10, 64)
		if err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid id value"})
			return
		}
		contractIDs = append(contractIDs, id)
	}

	// Fetch contracts
	contracts, total, st, err := contract.GetContracts(status, "", "", "", "", startTime, endTime, qStr, nil, contractIDs, &dateCreated, limit, offset)
	if err != nil {
		c.JSON(st, gin.H{"error": err.Error()})
		return
	}

	// Respond with the contracts and the total count
	c.JSON(http.StatusOK, gin.H{"total": total, "data": models.ConvertContractToListResponse(contracts)})
}

func GetContractStatuses(c *gin.Context) {

	// Respond with the contract statuses
	c.JSON(http.StatusOK, gin.H{"data": models.GetContractStatuses()})
}

func CreateContract(c *gin.Context) {

	var payload models.CreateContractRequestPayload

	if err := c.ShouldBindJSON(&payload); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON payload"})
		log.Printf("Invalid JSON payload: %v", err)
		return
	}

	db := shared.GetDb()

	newContract := models.Contract{
		BuyerID:          payload.BuyerID,
		SellerID:         payload.SellerID,
		Description:      payload.Description,
		ProductID:        payload.ProductID,
		MinTemp:          payload.MinTemp,
		MaxTemp:          payload.MaxTemp,
		ArrivalDate:      time.Unix(payload.ArrivalDate, 0),
		PenaltyType:      payload.PenaltyType,
		PenaltyValue:     payload.PenaltyValue,
		PenaltyRec:       payload.PenaltyRec,
		StartLat:         payload.StartLat,
		StartLon:         payload.StartLon,
		Status:           models.ContractStatusPending,
		StartPlaceName:   payload.StartPlaceName,
		EndPlaceName:     payload.EndPlaceName,
		Name:             payload.Name,
		EndLat:           payload.EndLat,
		EndLon:           payload.EndLon,
		BlockchainSecret: shared.GenerateRandomString(BlockchainSecretLength),
	}

	if err := db.Create(&newContract).Error; err != nil {
		log.Printf("SaveContractInfo CREATE - Contract DB Error: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create new contract"})
		return
	}

	log.Printf("Successfully received and saved contract: %v", newContract)
	c.JSON(http.StatusOK, gin.H{"message": "Successfully received and saved contract", "id": newContract.ID})
}

func GetContractByID(c *gin.Context) {
	// Get the contract ID from url parameters
	contractIDStr := c.Param("contract_id")

	if contractIDStr == "" {
		log.Printf("GetContractByID Error: ID is required")
		c.JSON(http.StatusBadRequest, gin.H{"error": "ID is required"})
		return
	}

	contractID, err := strconv.ParseUint(contractIDStr, 10, 32)
	if err != nil {
		log.Printf("GetContractByID Error: Invalid ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
		return
	}

	// Fetch contract
	contract, st, err := contract.GetContractByID(uint(contractID))
	if err != nil {
		c.JSON(st, gin.H{"error": err.Error()})
		return
	}

	// Respond with the contracts and the total count
	c.JSON(http.StatusOK, gin.H{"data": models.ConvertContractToContractResponse(contract)})
}

func UpdateContract(c *gin.Context) {

	var contractModel models.Contract
	rawData, _ := c.GetRawData()

	// Unmarshal to the important info structure
	err := json.Unmarshal(rawData, &contractModel)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON payload"})
		log.Printf("Invalid json pyload : %v", err)
		return
	}

	// Get the contract ID from url parameters
	contractIDStr := c.Param("contract_id")
	if contractIDStr == "" {
		log.Printf("UpdateContract Error: ID is required")
		c.JSON(http.StatusBadRequest, gin.H{"error": "ID is required"})
		return
	}

	contractID, err := strconv.ParseUint(contractIDStr, 10, 32)
	if err != nil {
		log.Printf("UpdateContract Error: Invalid ID: %v", err)
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
		return
	}

	contractModel.ID = uint(contractID)

	// update contract
	contractModel, st, err := contract.UpdateContract(contractModel)
	if err != nil {
		c.JSON(st, gin.H{"error": err.Error()})
		return
	}

	// Respond with the contracts and the total count
	c.JSON(http.StatusOK, gin.H{"data": models.ConvertContractToContractResponse(contractModel)})
}

func GetContractCountByStatus(c *gin.Context) {
	companyID := c.DefaultQuery("company_id", "")
	startTimeStr := c.DefaultQuery("start_time", "")
	endTimeStr := c.DefaultQuery("end_time", "")

	if companyID == "" || startTimeStr == "" || endTimeStr == "" {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "Missing required query parameters",
		})
		return
	}

	// Convert to uint and time.Time
	companyIDUint, err := strconv.ParseUint(companyID, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "Invalid companyID",
		})
		return
	}

	// Convert string to Unix timestamp
	startUnix, err := strconv.ParseInt(startTimeStr, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "Invalid start time",
		})
		return
	}

	endUnix, err := strconv.ParseInt(endTimeStr, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "Invalid end time",
		})
		return
	}

	// Convert Unix timestamps to time.Time
	startTime := time.Unix(startUnix, 0)
	endTime := time.Unix(endUnix, 0)

	activeCount, executedCount, _, monthly, err := contract.CountContractsByMultipleStatusesAndTotal(uint(companyIDUint), startTime, endTime)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Internal Server Error",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{"data": models.ActiveContractsResponse{ActiveCount: activeCount, ExecutedCount : executedCount, MonthlyContracts: monthly}})

}

func GetTotalContractCount(c *gin.Context) {
	companyID := c.DefaultQuery("company_id", "0")
	startTimeStr := c.DefaultQuery("start_time", "")
	endTimeStr := c.DefaultQuery("end_time", "")

	// Convert to uint and time.Time
	companyIDUint, err := strconv.ParseUint(companyID, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "Invalid companyID",
		})
		return
	}

	// Convert string to Unix timestamp
	startUnix, _ := strconv.ParseInt(startTimeStr, 10, 64)
	endUnix, _ := strconv.ParseInt(endTimeStr, 10, 64)

	// Convert Unix timestamps to time.Time
	startTime := time.Unix(startUnix, 0)
	endTime := time.Unix(endUnix, 0)

	_, _, totalCount, _, err := contract.CountContractsByMultipleStatusesAndTotal(uint(companyIDUint), startTime, endTime)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": "Internal Server Error",
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{"data" : totalCount})
}