package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"verofy-backend/models"
	"verofy-backend/qc"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/joho/godotenv"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

var db *gorm.DB

func getEnv(key, fallback string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return fallback
}

func initDB() {
	if err := godotenv.Load(); err != nil {
		log.Println("No .env file found")
	}

	dsn := fmt.Sprintf(
		"host=%s user=%s password=%s dbname=%s port=%s sslmode=require",
		getEnv("DB_HOST", "localhost"),
		getEnv("DB_USER", "postgres"),
		getEnv("DB_PASS", ""),
		getEnv("DB_NAME", "verofy"),
		getEnv("DB_PORT", "5432"),
	)

	var err error
	db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
}

// Helper function to parse geometry from json.RawMessage
func parseGeometry(rawGeometry json.RawMessage) interface{} {
	if len(rawGeometry) == 0 {
		return nil
	}

	var geometry interface{}
	if err := json.Unmarshal(rawGeometry, &geometry); err != nil {
		log.Printf("Failed to parse geometry: %v", err)
		return nil
	}
	return geometry
}

func main() {
	initDB()

	// Define configuration variables
	schema := getEnv("SCHEMA_NAME", "eli_test")
	segmentTable := getEnv("SEGMENT_TABLE", "segment2")
	zoneCol := getEnv("ZONE_COLUMN", "group_1")
	mapIDCol := getEnv("MAPID_COLUMN", "mapid")
	idCol := getEnv("ID_COLUMN", "id")
	qcFlagCol := getEnv("QCFLAG_COLUMN", "qc_flag")
	serverPort := getEnv("SERVER_PORT", "8080")

	router := gin.Default()
	router.Use(cors.Default())

	router.Static("/static", "../Frontend")

	router.GET("/", func(c *gin.Context) {
		c.File("../Frontend/index.html")
	})

	// Register QC routes
	qc.GraphConnectivityRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
	qc.SingleSpanRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
	qc.SiteConnectivityRoute(router, db, schema)
	qc.UndergroundEndpointsRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
	qc.AerialEndpointsRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)
	qc.ZoneContainmentRoute(router, db, schema, segmentTable, mapIDCol, zoneCol, idCol, qcFlagCol)

	router.GET("/api/markets", func(c *gin.Context) {
		var markets []models.MarketOption
		table := fmt.Sprintf("%s.map_projects", schema)
		db.Table(table).Select("mapid, TRIM(project) as project").Where("mapid IS NOT NULL").Order("project").Scan(&markets)
		c.JSON(http.StatusOK, markets)
	})

	router.GET("/api/zones", func(c *gin.Context) {
		mapID := c.Query("map_id")
		if mapID == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Missing map_id query parameter"})
			return
		}

		var zones []string
		table := fmt.Sprintf("%s.%s", schema, segmentTable)
		db.Table(table).Where(fmt.Sprintf("%s = ? AND %s IS NOT NULL", mapIDCol, zoneCol), mapID).Distinct(zoneCol).Pluck(zoneCol, &zones)
		c.JSON(http.StatusOK, zones)
	})

	router.GET("/api/segments", func(c *gin.Context) {
		mapID := c.Query("map_id")
		zone := c.Query("zone")

		var segments []struct {
			ID0              int             `gorm:"column:id_0"`
			MapID            int             `gorm:"column:mapid"`
			SegmentType      string          `gorm:"column:segment_type"`
			SegmentStatus    string          `gorm:"column:segment_status"`
			ID               int             `gorm:"column:id"`
			ProtectionStatus string          `gorm:"column:protection_status"`
			QCFlag           string          `gorm:"column:qc_flag"`
			Group1           *string         `gorm:"column:group_1"`
			Geometry         json.RawMessage `gorm:"column:geometry"`
		}
		table := fmt.Sprintf("%s.%s", schema, segmentTable)
		query := db.Table(table).Select(fmt.Sprintf("id_0, %s, segment_type, segment_status, %s, protection_status, %s, \"%s\" as group_1, ST_AsGeoJSON(ST_Transform(geom, 4326))::json AS geometry", mapIDCol, idCol, qcFlagCol, zoneCol))

		if mapID != "" {
			query = query.Where(fmt.Sprintf("%s = ?", mapIDCol), mapID)
		}
		if zone != "" {
			query = query.Where(fmt.Sprintf("\"%s\" = ?", zoneCol), zone)
		}

		query.Find(&segments)

		features := []map[string]interface{}{}
		for _, s := range segments {
			features = append(features, map[string]interface{}{
				"type":     "Feature",
				"geometry": parseGeometry(s.Geometry),
				"properties": map[string]interface{}{
					"id_0":              s.ID0,
					"mapid":             s.MapID,
					"segment_type":      s.SegmentType,
					"segment_status":    s.SegmentStatus,
					"id":                s.ID,
					"protection_status": s.ProtectionStatus,
					"qc_flag":           s.QCFlag,
					"group_1":           s.Group1,
				},
			})
		}

		c.JSON(http.StatusOK, map[string]interface{}{
			"type":     "FeatureCollection",
			"features": features,
		})
	})

	// SITES
	router.GET("/api/sites", func(c *gin.Context) {
		mapID := c.Query("mapprojectid")

		var sites []struct {
			GID          int             `gorm:"column:gid"`
			ID           *int            `gorm:"column:id"`
			MapProjectID *int            `gorm:"column:mapprojectid"`
			Name         *string         `gorm:"column:name"`
			Address1     *string         `gorm:"column:address"`
			City         *string         `gorm:"column:city"`
			State        *string         `gorm:"column:state"`
			Zip          *string         `gorm:"column:zip"`
			Group1       *string         `gorm:"column:group1"`
			Geometry     json.RawMessage `gorm:"column:geometry"`
		}
		table := fmt.Sprintf("%s.sites", schema)
		query := db.Table(table).Select("gid, id, \"MapProjectID\" as mapprojectid, \"Name\" as name, \"Address1\" as address, \"City\" as city, \"State\" as state, \"Zip\" as zip, \"Group 1\" as group1, ST_AsGeoJSON(geometry)::json AS geometry")

		if mapID != "" {
			query = query.Where("\"MapProjectID\" = ?", mapID)
		}

		query.Find(&sites)

		features := []map[string]interface{}{}
		for _, s := range sites {
			features = append(features, map[string]interface{}{
				"type":     "Feature",
				"geometry": parseGeometry(s.Geometry),
				"properties": map[string]interface{}{
					"gid":          s.GID,
					"id":           s.ID,
					"mapprojectid": s.MapProjectID,
					"name":         s.Name,
					"address":      s.Address1,
					"city":         s.City,
					"state":        s.State,
					"zip":          s.Zip,
					"group1":       s.Group1,
				},
			})
		}

		c.JSON(http.StatusOK, map[string]interface{}{
			"type":     "FeatureCollection",
			"features": features,
		})
	})

	// POLES
	router.GET("/api/poles", func(c *gin.Context) {
		mapID := c.Query("map_id")

		var poles []models.PolesGeoJSON
		table := fmt.Sprintf("%s.poles", schema)
		query := db.Table(table).Select("gid, id, mapprojectid, name, tags, group1, group2, owner, poleheight, attachmentheight, ST_AsGeoJSON(ST_Transform(geom, 4326))::json AS geometry")

		if mapID != "" {
			query = query.Where("mapprojectid = ?", mapID)
		}

		query.Find(&poles)

		features := []map[string]interface{}{}
		for _, p := range poles {
			features = append(features, map[string]interface{}{
				"type":     "Feature",
				"geometry": parseGeometry(p.Geometry),
				"properties": map[string]interface{}{
					"gid":              p.GID,
					"id":               p.ID,
					"mapprojectid":     p.MapProjectID,
					"name":             p.Name,
					"tags":             p.Tags,
					"group1":           p.Group1,
					"group2":           p.Group2,
					"owner":            p.Owner,
					"poleheight":       p.PoleHeight,
					"attachmentheight": p.AttachmentHeight,
				},
			})
		}

		c.JSON(http.StatusOK, map[string]interface{}{
			"type":     "FeatureCollection",
			"features": features,
		})
	})

	// Access_Points
	router.GET("/api/access_points", func(c *gin.Context) {
		mapID := c.Query("map_id")

		var accessPoints []models.AccessPointGeoJSON
		table := fmt.Sprintf("%s.access_points", schema)
		query := db.Table(table).Select(`
			gid, id, name, mapprojectid, latitude, longitude, manufacturer, size, locked, description, aka,
			createdby, createddate, modifiedby, modifieddate, historyid, group1, group2, typeid, statusid,
			crmvendorid, billdate,
			ST_AsGeoJSON(ST_Transform(geom, 4326))::json AS geometry
		`)

		if mapID != "" {
			query = query.Where("mapprojectid = ?", mapID)
		}

		query.Find(&accessPoints)

		features := []map[string]interface{}{}
		for _, ap := range accessPoints {
			features = append(features, map[string]interface{}{
				"type":     "Feature",
				"geometry": parseGeometry(ap.Geometry),
				"properties": map[string]interface{}{
					"gid":          ap.GID,
					"id":           ap.ID,
					"name":         ap.Name,
					"mapprojectid": ap.MapProjectID,
					"latitude":     ap.Latitude,
					"longitude":    ap.Longitude,
					"manufacturer": ap.Manufacturer,
					"size":         ap.Size,
					"locked":       ap.Locked,
					"description":  ap.Description,
					"aka":          ap.AKA,
					"createdby":    ap.CreatedBy,
					"createddate":  ap.CreatedDate,
					"modifiedby":   ap.ModifiedBy,
					"modifieddate": ap.ModifiedDate,
					"historyid":    ap.HistoryID,
					"group1":       ap.Group1,
					"group2":       ap.Group2,
					"typeid":       ap.TypeID,
					"statusid":     ap.StatusID,
					"crmvendorid":  ap.CRMVendorID,
					"billdate":     ap.BillDate,
				},
			})
		}

		c.JSON(http.StatusOK, map[string]interface{}{
			"type":     "FeatureCollection",
			"features": features,
		})
	})

	// Info Objects - FIXED
	router.GET("/api/info", func(c *gin.Context) {
		mapID := c.Query("map_id")

		var infos []models.InfoGeoJSON
		table := fmt.Sprintf("%s.info", schema)
		query := db.Table(table).Select(`
			id, name, tags, description, group_1, group_2,
			ST_AsGeoJSON(geom)::json AS geometry
		`)

		if mapID != "" {
			query = query.Where("mapprojectid = ?", mapID)
		}

		if err := query.Find(&infos).Error; err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		features := []map[string]interface{}{}
		for _, info := range infos {
			features = append(features, map[string]interface{}{
				"type":     "Feature",
				"geometry": parseGeometry(info.Geometry),
				"properties": map[string]interface{}{
					"id":          info.ID,
					"name":        info.Name,
					"tags":        info.Tags,
					"description": info.Description,
					"group_1":     info.Group1,
					"group_2":     info.Group2,
				},
			})
		}

		c.JSON(http.StatusOK, map[string]interface{}{
			"type":     "FeatureCollection",
			"features": features,
		})
	})

	// Server Start
	log.Printf("Server is running on http://localhost:%s", serverPort)
	if err := router.Run(":" + serverPort); err != nil {
		log.Fatal("Server failed:", err)
	}
}
