package tasks

import (
	"context"
	"database/sql"
	"log/slog"
	"time"
	"vctp/db"
	"vctp/db/queries"
	"vctp/internal/vcenter"

	"github.com/vmware/govmomi/vim25/types"
)

// Handler handles requests.
type CronTask struct {
	Logger   *slog.Logger
	Database db.Database
}

// use gocron to check events in the Events table
func (c *CronTask) RunVmCheck(ctx context.Context, logger *slog.Logger) error {
	var (
		numVcpus       int32
		numRam         int32
		totalDiskGB    float64
		srmPlaceholder int
		foundVm        bool
		isTemplate     int
		poweredOn      int
		folderPath     string
	)

	dateCmp := time.Now().AddDate(0, 0, -1).Unix()
	logger.Debug("Started Events processing", "time", time.Now(), "since", dateCmp)

	// Query events table
	events, err := c.Database.Queries().ListUnprocessedEvents(ctx,
		sql.NullInt64{Int64: dateCmp, Valid: dateCmp > 0})
	if err != nil {
		logger.Error("Unable to query for unprocessed events", "error", err)
		return nil // TODO - what to do with this error?
	}

	for _, evt := range events {
		logger.Debug("Checking event", "event", evt)

		// TODO - get a list of unique vcenters, then process each event in batches
		// to avoid doing unnecessary login/logout of vcenter

		c.Logger.Debug("connecting to vcenter")
		vc := vcenter.New(c.Logger)
		vc.Login(evt.Source)

		//datacenter = evt.DatacenterName.String
		vmObject, err := vc.FindVMByIDWithDatacenter(evt.VmId.String, evt.DatacenterId.String)

		if err != nil {
			c.Logger.Error("Can't locate vm in vCenter", "vmID", evt.VmId.String, "error", err)
		} else if vmObject == nil {
			c.Logger.Debug("didn't find VM", "vm_id", evt.VmId.String)
			numRam = 0
			numVcpus = 0
			totalDiskGB = 0
			isTemplate = 0
			folderPath = ""
		} else {
			c.Logger.Debug("found VM")
			srmPlaceholder = 0 // Default assumption
			//prettyPrint(vmObject)

			// calculate VM properties we want to store
			if vmObject.Vm.Config != nil {
				numRam = vmObject.Vm.Config.Hardware.MemoryMB
				numVcpus = vmObject.Vm.Config.Hardware.NumCPU

				// Calculate the total disk allocated in GB
				for _, device := range vmObject.Vm.Config.Hardware.Device {
					if disk, ok := device.(*types.VirtualDisk); ok {
						totalDiskGB += float64(disk.CapacityInBytes / 1024 / 1024 / 1024) // Convert from bytes to GB
					}
				}

				// Determine if the VM is a normal VM or an SRM placeholder
				if vmObject.Vm.Config.ManagedBy != nil && vmObject.Vm.Config.ManagedBy.Type == "com.vmware.vcDr" {
					c.Logger.Debug("VM ManagedBy indicates managed by SRM")
					srmPlaceholder = 1
				}

				if vmObject.Vm.Config.Template {
					isTemplate = 1
				} else {
					isTemplate = 0
				}

				// Retrieve the full folder path of the VM
				folderPath, err = vc.GetVMFolderPath(vmObject.Vm)
				if err != nil {
					c.Logger.Error("failed to get vm folder path", "error", err)
					folderPath = ""
				} else {
					c.Logger.Debug("Found vm folder path", "folder_path", folderPath)
				}

				foundVm = true
			} else {
				c.Logger.Error("Empty VM config")
			}

			//if (types.VirtualMachineRuntimeInfo{}) != vmObject.Vm.Runtime {
			//if runtime, ok := vmObject.Vm.Runtime.(types.VirtualMachineRuntimeInfo); ok {
			c.Logger.Debug("VM has runtime data", "power_state", vmObject.Vm.Runtime.PowerState)
			if vmObject.Vm.Runtime.PowerState == "" {
				poweredOn = 0
			} else {
				poweredOn = 1
			}
			//}

		}
		err = vc.Logout()
		if err != nil {
			c.Logger.Error("unable to logout of vcenter", "error", err)
		}

		if foundVm {
			c.Logger.Debug("Adding to Inventory table", "vm_name", evt.VmName.String, "vcpus", numVcpus, "ram", numRam, "dc", evt.DatacenterId.String)

			params := queries.CreateInventoryParams{
				Name:            vmObject.Vm.Name,
				Vcenter:         evt.Source,
				CloudId:         sql.NullString{String: evt.CloudId, Valid: evt.CloudId != ""},
				EventKey:        sql.NullString{String: evt.EventKey.String, Valid: evt.EventKey.Valid},
				VmId:            sql.NullString{String: evt.VmId.String, Valid: evt.VmId.Valid},
				Datacenter:      sql.NullString{String: evt.DatacenterName.String, Valid: evt.DatacenterName.Valid},
				Cluster:         sql.NullString{String: evt.ComputeResourceName.String, Valid: evt.ComputeResourceName.Valid},
				CreationTime:    sql.NullInt64{Int64: evt.EventTime.Int64, Valid: evt.EventTime.Valid},
				InitialVcpus:    sql.NullInt64{Int64: int64(numVcpus), Valid: numVcpus > 0},
				InitialRam:      sql.NullInt64{Int64: int64(numRam), Valid: numRam > 0},
				ProvisionedDisk: sql.NullFloat64{Float64: totalDiskGB, Valid: totalDiskGB > 0},
				Folder:          sql.NullString{String: folderPath, Valid: folderPath != ""},
				ResourcePool:    sql.NullString{String: vmObject.ResourcePool, Valid: vmObject.ResourcePool != ""},
				SrmPlaceholder:  sql.NullInt64{Int64: int64(srmPlaceholder), Valid: true},
				IsTemplate:      sql.NullInt64{Int64: int64(isTemplate), Valid: true},
				PowerState:      sql.NullInt64{Int64: int64(poweredOn), Valid: true},
			}

			c.Logger.Debug("database params", "params", params)

			// Insert the new inventory record into the database
			result, err := c.Database.Queries().CreateInventory(ctx, params)
			if err != nil {
				c.Logger.Error("unable to perform database insert", "error", err)
			} else {
				c.Logger.Debug("created database record", "insert_result", result)

				// mark this event as processed
				err = c.Database.Queries().UpdateEventsProcessed(ctx, evt.Eid)
				if err != nil {
					c.Logger.Error("Unable to mark this event as processed", "event_id", evt.Eid, "error", err)
				} else {
					c.Logger.Debug("Marked event as processed", "event_id", evt.Eid)
				}
			}
		} else {
			c.Logger.Debug("Not adding to Inventory due to missing vcenter config property", "vm_name", evt.VmName.String)
		}

	}

	//fmt.Printf("processing at %s", time.Now())
	return nil
}
