package controllers

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	"gitlab.com/pactual1/backend/models"
	"gitlab.com/pactual1/backend/shared"
)

func SaveDeviceInfo(c *gin.Context) {
	var deviceInfo models.DeviceInfo
	rawData, _ := c.GetRawData()

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

	deviceInfo.RawJSON = string(rawData)
	deviceInfo.X = deviceInfo.AccInfo.X
	deviceInfo.Y = deviceInfo.AccInfo.Y
	deviceInfo.Z = deviceInfo.AccInfo.Z

		// Attempt to find the device by IMEI; if not found, create a new device
		var device models.Device
		if err := shared.GetDb().Unscoped().Where("device_id = ?", deviceInfo.ExternalDeviceID).First(&device).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				// Create new device
				newDevice := models.Device{
					IMEI: deviceInfo.IMEI,
					IMSI: deviceInfo.IMSI,
					DeviceID: deviceInfo.ExternalDeviceID,
					DeviceConfiguration: string(rawData),
				}
				if err := shared.GetDb().Create(&newDevice).Error; err != nil {
					log.Printf("CREATE -Device DB Error: %v",err)
					c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not create new device"})
					return
				}
				deviceInfo.DeviceID = newDevice.ID
			} else {
				log.Printf("CREATE -Device DB Error: %v",err)
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
				return
			}
		} else {
			log.Printf("Current device deleted at: %v",device.DeletedAt)

			if device.DeletedAt != nil {
				// Use raw SQL to update the record
				if err := shared.GetDb().Exec("UPDATE devices SET deleted_at = NULL, company_id = NULL WHERE id = ?", device.ID).Error; err != nil {
					log.Printf("UNDELETE -Device DB Error: %v", err)
					c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not revive deleted device"})
					return
				} else {
					log.Printf("Device undeleted successfuly : %v",device.DeletedAt)
				}
			}
			deviceInfo.DeviceID = device.ID
		}


	// Save deviceInfo to your database
	if err := shared.GetDb().Create(&deviceInfo).Error; err != nil {
		log.Printf("SaveDeviceInfo CREATE -DeviceInfo DB Error: %v",err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not save device info"})
		return
	}
	log.Printf("Successfully received and saved device info: %v",deviceInfo)
	c.JSON(http.StatusOK, gin.H{"message": "Successfully received and saved device info", "data": deviceInfo})
}

func GetDeviceData(c *gin.Context) {
    // Get the device ID and contract ID from query parameters
    deviceIDStr := c.DefaultQuery("device_id", "")
    contractIDStr := c.DefaultQuery("contract_id", "")

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

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

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

    // Fetch the contract creation date based on contractID
    var contract models.Contract
    if err := shared.GetDb().Where("id = ?", contractID).First(&contract).Error; err != nil {
        log.Printf("GetDeviceData Error: Could not fetch contract: %v", err)
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Could not fetch contract"})
        return
    }

	deviceConnectedToContract := false	
	for _, contractDeviceID := range contract.DeviceIDs {
		if deviceID == uint64(contractDeviceID) {
			deviceConnectedToContract = true
		}
	}

	if !deviceConnectedToContract {
        log.Printf("Device %v is not connected to contract %v", deviceID, contractID )
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Device is not present int his contract"})
        return
    }

    var deviceInfos []models.DeviceInfo
    // Modify your query to include filtering based on the contract's creation date
    query := shared.GetDb().Where("device_id = ?", deviceID).Where("created_at >= ? AND created_at <= ?", contract.StartTime,contract.EndTime)

    if err := query.Find(&deviceInfos).Error; err != nil {
        if errors.Is(err, gorm.ErrRecordNotFound) {
            log.Printf("GetDeviceData Error: No device info found: %v", err)
            c.JSON(http.StatusNotFound, gin.H{"error": "No device info found"})
        } else {
            log.Printf("GetDeviceData Error: Database error: %v", err)
            c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
        }
        return
    }

    // Create a GeoJSON feature collection
    var featureCollection models.GeoJSONFeatureCollection
    featureCollection.Type = "FeatureCollection"
    featureCollection.Features = []models.GeoJSONFeature{}

    // Loop through each deviceInfo to create GeoJSON features
    for _, info := range deviceInfos {
        info.RawJSON = ""
        feature := models.GeoJSONFeature{
            Type: "Feature",
            DeviceInfo: &info,
            Geometry: models.GeoJSONGeometry{
                Type:        "Point",
                Coordinates: []float64{info.Lon, info.Lat},
            },
            Properties: map[string]interface{}{},
        }
        featureCollection.Features = append(featureCollection.Features, feature)
    }

    // Respond with the GeoJSON feature collection
    c.JSON(http.StatusOK, featureCollection)
}



func GetDevicesByContract(c *gin.Context) {
	// Get the contract ID from query parameter
	contractIDStr := c.DefaultQuery("contract_id", "")
	if contractIDStr == "" {
		log.Printf("GetDevicesByContract Error: Contract ID is required")
		c.JSON(http.StatusBadRequest, gin.H{"error": "Contract ID is required"})
		return
	}

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

	log.Printf("This is the  ID: %v", contractID)
	// Fetch the contract from the database
	var contract models.Contract
	if err := shared.GetDb().Where("id = ?", contractID).First(&contract).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			log.Printf("GetDevicesByContract Error: No contract found: %v", err)
			c.JSON(http.StatusNotFound, gin.H{"error": "No contract found"})
			return
		} else {
			log.Printf("GetDevicesByContract Error: Database error: %v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
			return
		}
	}
	log.Printf("This is the device IDS ID: %v", contract.DeviceIDs)
	// Create a slice to hold the devices
	var devices []models.Device

	// Convert the integer array to a comma-separated string
	idStrings := []string{}
	for _, id := range contract.DeviceIDs {
		idStrings = append(idStrings, strconv.FormatInt(id, 10))
	}
	idStr := strings.Join(idStrings, ",")

	// Construct the SQL query manually
	sqlQuery := fmt.Sprintf("SELECT * FROM devices WHERE id IN (%s)", idStr)

	// Fetch devices from the database based on DeviceIDs in the contract
	// Execute the query
	if err := shared.GetDb().Raw(sqlQuery).Scan(&devices).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			log.Printf("GetDevicesByContract Error: No devices found: %v", err)
			c.JSON(http.StatusNotFound, gin.H{"error": "No devices found"})
		} else {
			log.Printf("GetDevicesByContract Error: Database error: %v", err)
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error"})
		}
		return
	}

	// Respond with the devices
	c.JSON(http.StatusOK, devices)
}


