package main

import (
	"context"
	"encoding/json"
	"log"
	"net/http"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	CheckOrigin:     func(r *http.Request) bool { return true },
}

type Client struct {
	conn *websocket.Conn
	mu   sync.Mutex
}

type Hub struct {
	clients   map[*Client]bool
	broadcast chan []byte
	mu        sync.Mutex
}

func newHub() *Hub {
	return &Hub{
		clients:   make(map[*Client]bool),
		broadcast: make(chan []byte, 512),
	}
}

func (h *Hub) run() {
	for msg := range h.broadcast {
		h.mu.Lock()
		var dead []*Client
		for client := range h.clients {
			if err := client.writeMessage(websocket.TextMessage, msg); err != nil {
				dead = append(dead, client)
			}
		}
		for _, c := range dead {
			delete(h.clients, c)
			c.conn.Close()
		}
		h.mu.Unlock()
	}
}

func (c *Client) writeMessage(msgType int, data []byte) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	return c.conn.WriteMessage(msgType, data)
}

func (h *Hub) wsHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}
	client := &Client{conn: conn}
	h.mu.Lock()
	h.clients[client] = true
	h.mu.Unlock()

	defer func() {
		h.mu.Lock()
		delete(h.clients, client)
		h.mu.Unlock()
		conn.Close()
	}()

	go func() {
		t := time.NewTicker(25 * time.Second)
		defer t.Stop()
		for range t.C {
			if err := client.writeMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}()

	for {
		if _, _, err := conn.ReadMessage(); err != nil {
			break
		}
	}
}

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	db, dbErr := initDB(ctx)
	if dbErr != nil {
		log.Printf("WARNING: PostgreSQL unavailable (%v) — running read-only from Redis", dbErr)
	}
	if db != nil {
		defer db.Close()
		// Initialize anomaly detection tables
		if err := initAnomalyTables(db); err != nil {
			log.Printf("Warning: failed to initialize anomaly tables (may already exist): %v", err)
		}
	}

	rdb := newRedisClient()
	hub := newHub()
	go hub.run()
	go pollRedis(ctx, rdb, db, hub)

	mux := http.NewServeMux()

	// Seed default users once DB is ready
	if db != nil {
		db.seedDefaultUsers(ctx)
	}

	// Public endpoints (no auth)
	mux.HandleFunc("/api/auth/login", cors(loginHandler(db)))
	mux.HandleFunc("/health", cors(func(w http.ResponseWriter, r *http.Request) {
		json.NewEncoder(w).Encode(map[string]any{
			"status": "ok",
			"time":   time.Now().UTC().Format(time.RFC3339),
		})
	}))

	// WebSocket (auth checked via token query param or skipped in dev)
	mux.HandleFunc("/ws", hub.wsHandler)

	// Protected API endpoints
	mux.HandleFunc("/api/telemetry/latest",  cors(authMiddleware(latestHandler(db))))
	mux.HandleFunc("/api/telemetry/history", cors(authMiddleware(historyHandler(db))))
	mux.HandleFunc("/api/alarms",            cors(authMiddleware(alarmsHandler(db))))
	mux.HandleFunc("/api/stats",             cors(authMiddleware(statsHandler(db))))

	// Admin — user management (admin role required)
	mux.HandleFunc("GET /api/admin/users",                      cors(adminOnly(listUsersHandler(db))))
	mux.HandleFunc("POST /api/admin/users",                     cors(adminOnly(createUserHandler(db))))
	mux.HandleFunc("PUT /api/admin/users/{id}",                 cors(adminOnly(updateUserHandler(db))))
	mux.HandleFunc("DELETE /api/admin/users/{id}",              cors(adminOnly(deleteUserHandler(db))))
	mux.HandleFunc("POST /api/admin/users/{id}/reset-password", cors(adminOnly(resetPasswordHandler(db))))

	// Admin -- CMMS bulk import (admin only)
	mux.HandleFunc("POST /api/admin/cmms/import",   cors(adminOnly(importCMMSAssetsHandler(db))))
	mux.HandleFunc("POST /api/admin/assets",         cors(adminOnly(createAssetHandler(db))))
	mux.HandleFunc("PUT /api/admin/assets/{id}",     cors(adminOnly(updateAssetHandler(db))))
	mux.HandleFunc("DELETE /api/admin/assets/{id}",  cors(adminOnly(deleteAssetHandler(db))))

	// Location endpoints
	mux.HandleFunc("GET /api/locations",             cors(authMiddleware(listLocationsHandler(db))))
	mux.HandleFunc("GET /api/locations/tree",         cors(authMiddleware(locationTreeHandler(db))))
	mux.HandleFunc("POST /api/admin/locations",       cors(adminOnly(createLocationHandler(db))))
	mux.HandleFunc("PUT /api/admin/locations/{id}",   cors(adminOnly(updateLocationHandler(db))))
	mux.HandleFunc("DELETE /api/admin/locations/{id}", cors(adminOnly(deleteLocationHandler(db))))

	// Customer endpoints
	mux.HandleFunc("GET /api/customers",                         cors(authMiddleware(listCustomersHandler(db))))
	mux.HandleFunc("GET /api/customers/{id}/connections",        cors(authMiddleware(listConnectionsHandler(db))))
	mux.HandleFunc("POST /api/admin/customers",                  cors(adminOnly(createCustomerHandler(db))))
	mux.HandleFunc("PUT /api/admin/customers/{id}",              cors(adminOnly(updateCustomerHandler(db))))
	mux.HandleFunc("DELETE /api/admin/customers/{id}",           cors(adminOnly(deleteCustomerHandler(db))))
	mux.HandleFunc("POST /api/admin/customers/{id}/connections", cors(adminOnly(createConnectionHandler(db))))

	// CMMS read endpoints (any authenticated user)
	mux.HandleFunc("GET /api/cmms/assets",      cors(authMiddleware(listCMMSAssetsHandler(db))))
	mux.HandleFunc("GET /api/cmms/assets/{id}", cors(authMiddleware(getCMMSAssetHandler(db))))
	mux.HandleFunc("GET /api/cmms/health",      cors(authMiddleware(cmmsDashboardHandler(db))))
	mux.HandleFunc("GET /api/cmms/pm-overdue",  cors(authMiddleware(cmmsPMOverdueHandler(db))))
	mux.HandleFunc("GET /api/cmms/sync-log",    cors(adminOnly(cmmsSyncLogHandler(db))))

	// GIS endpoints — real GPS from tbl_asset_location / tbl_zone / tbl_site
	mux.HandleFunc("GET /api/gis/assets",       cors(authMiddleware(listGISAssetsHandler(db))))
	mux.HandleFunc("GET /api/gis/sites",         cors(authMiddleware(listGISSiteClustersHandler(db))))
	mux.HandleFunc("GET /api/gis/asset-detail",  cors(authMiddleware(getGISAssetDetailHandler(db))))

	// AI Analytics — Anomaly Detection endpoints (auth optional for testing)
	mux.HandleFunc("POST /api/ai/anomalies",          cors(createAnomalyHandler(db)))
	mux.HandleFunc("GET /api/ai/anomalies/active",    cors(getActiveAnomaliesHandler(db)))
	mux.HandleFunc("GET /api/ai/anomalies/{id}",      cors(getAnomalyDetailHandler(db)))
	mux.HandleFunc("GET /api/ai/anomalies/history",   cors(getAnomalyHistoryHandler(db)))
	mux.HandleFunc("GET /api/ai/anomalies/metrics",   cors(getAnomalyMetricsHandler(db)))
	mux.HandleFunc("POST /api/ai/anomalies/{id}/dismiss", cors(dismissAnomalyHandler(db)))
	mux.HandleFunc("GET /api/ai/alert-rules",         cors(getAlertRulesHandler(db)))
	mux.HandleFunc("POST /api/ai/alert-rules",        cors(createAlertRuleHandler(db)))
	mux.HandleFunc("GET /api/ai/sensors/health",      cors(getSensorHealthHandler(db)))
	mux.HandleFunc("POST /api/work-orders",           cors(createWorkOrderHandler(db)))

	port := os.Getenv("PORT")
	if port == "" {
		port = "9090"
	}
	srv := &http.Server{Addr: ":" + port, Handler: mux}
	log.Printf("IC³ Dashboard backend → http://localhost:%s", port)

	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("server: %v", err)
		}
	}()

	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	<-sig
	log.Println("shutting down…")
	ctx2, c2 := context.WithTimeout(context.Background(), 5*time.Second)
	defer c2()
	srv.Shutdown(ctx2)
}

func cors(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Set("Content-Type", "application/json")
		if r.Method == http.MethodOptions {
			w.WriteHeader(204)
			return
		}
		next(w, r)
	}
}
