package monitors

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

	"github.com/henrygd/beszel/internal/entities/monitor"
	"github.com/pocketbase/dbx"
	"github.com/pocketbase/pocketbase/core"
)

// APIHandler handles monitor API endpoints
type APIHandler struct {
	app       core.App
	scheduler *Scheduler
}

// NewAPIHandler creates a new monitor API handler
func NewAPIHandler(app core.App, scheduler *Scheduler) *APIHandler {
	return &APIHandler{
		app:       app,
		scheduler: scheduler,
	}
}

// RegisterRoutes registers monitor API routes
func (h *APIHandler) RegisterRoutes(se *core.ServeEvent) {
	api := se.Router.Group("/api/beszel/monitors")

	// Require auth for all routes
	api.BindFunc(func(e *core.RequestEvent) error {
		if e.Auth == nil {
			return e.UnauthorizedError("Authentication required", nil)
		}
		return e.Next()
	})

	// CRUD endpoints
	api.GET("", h.listMonitors)
	api.POST("", h.createMonitor)
	api.GET("/:id", h.getMonitor)
	api.PATCH("/:id", h.updateMonitor)
	api.DELETE("/:id", h.deleteMonitor)

	// Action endpoints
	api.POST("/:id/check", h.manualCheck)
	api.POST("/:id/pause", h.pauseMonitor)
	api.POST("/:id/resume", h.resumeMonitor)
	api.GET("/:id/stats", h.getStats)
	api.GET("/:id/heartbeats", h.getHeartbeats)
}

// HeartbeatSummary represents a minimal heartbeat for the monitor list
type HeartbeatSummary struct {
	Status string    `json:"status"`
	Ping   int       `json:"ping"`
	Time   time.Time `json:"time"`
}

// MonitorResponse represents a monitor in API responses
type MonitorResponse struct {
	ID                     string             `json:"id"`
	Name                   string             `json:"name"`
	Type                   string             `json:"type"`
	URL                    string             `json:"url,omitempty"`
	Hostname               string             `json:"hostname,omitempty"`
	Port                   int                `json:"port,omitempty"`
	Method                 string             `json:"method,omitempty"`
	Interval               int                `json:"interval"`
	Timeout                int                `json:"timeout"`
	Retries                int                `json:"retries"`
	Status                 string             `json:"status"`
	Active                 bool               `json:"active"`
	Description            string             `json:"description,omitempty"`
	LastCheck              *time.Time         `json:"last_check,omitempty"`
	UptimeStats            map[string]float64 `json:"uptime_stats,omitempty"`
	RecentHeartbeats       []HeartbeatSummary `json:"recent_heartbeats,omitempty"`
	Tags                   []string           `json:"tags,omitempty"`
	Keyword                string             `json:"keyword,omitempty"`
	JSONQuery              string             `json:"json_query,omitempty"`
	ExpectedValue          string             `json:"expected_value,omitempty"`
	InvertKeyword          bool               `json:"invert_keyword"`
	DNSResolveServer       string             `json:"dns_resolve_server,omitempty"`
	DNSResolverMode        string             `json:"dns_resolver_mode,omitempty"`
	CertExpiryNotification bool               `json:"cert_expiry_notification"`
	CertExpiryDays         int                `json:"cert_expiry_days,omitempty"`
	IgnoreTLSError         bool               `json:"ignore_tls_error"`
	Created                time.Time          `json:"created"`
	Updated                time.Time          `json:"updated"`
}

// CreateMonitorRequest represents a request to create a monitor
type CreateMonitorRequest struct {
	Name                   string   `json:"name"`
	Type                   string   `json:"type"`
	URL                    string   `json:"url,omitempty"`
	Hostname               string   `json:"hostname,omitempty"`
	Port                   int      `json:"port,omitempty"`
	Method                 string   `json:"method,omitempty"`
	Headers                string   `json:"headers,omitempty"`
	Body                   string   `json:"body,omitempty"`
	Interval               int      `json:"interval"`
	Timeout                int      `json:"timeout"`
	Retries                int      `json:"retries,omitempty"`
	RetryInterval          int      `json:"retry_interval,omitempty"`
	MaxRedirects           int      `json:"max_redirects,omitempty"`
	Keyword                string   `json:"keyword,omitempty"`
	JSONQuery              string   `json:"json_query,omitempty"`
	ExpectedValue          string   `json:"expected_value,omitempty"`
	InvertKeyword          bool     `json:"invert_keyword,omitempty"`
	DNSResolveServer       string   `json:"dns_resolve_server,omitempty"`
	DNSResolverMode        string   `json:"dns_resolver_mode,omitempty"`
	Description            string   `json:"description,omitempty"`
	Tags                   []string `json:"tags,omitempty"`
	CertExpiryNotification bool     `json:"cert_expiry_notification,omitempty"`
	CertExpiryDays         int      `json:"cert_expiry_days,omitempty"`
	IgnoreTLSError         bool     `json:"ignore_tls_error,omitempty"`
}

// UpdateMonitorRequest represents a request to update a monitor
type UpdateMonitorRequest struct {
	Name                   *string  `json:"name,omitempty"`
	URL                    *string  `json:"url,omitempty"`
	Hostname               *string  `json:"hostname,omitempty"`
	Port                   *int     `json:"port,omitempty"`
	Method                 *string  `json:"method,omitempty"`
	Headers                *string  `json:"headers,omitempty"`
	Body                   *string  `json:"body,omitempty"`
	Interval               *int     `json:"interval,omitempty"`
	Timeout                *int     `json:"timeout,omitempty"`
	Retries                *int     `json:"retries,omitempty"`
	RetryInterval          *int     `json:"retry_interval,omitempty"`
	MaxRedirects           *int     `json:"max_redirects,omitempty"`
	Keyword                *string  `json:"keyword,omitempty"`
	JSONQuery              *string  `json:"json_query,omitempty"`
	ExpectedValue          *string  `json:"expected_value,omitempty"`
	InvertKeyword          *bool    `json:"invert_keyword,omitempty"`
	DNSResolveServer       *string  `json:"dns_resolve_server,omitempty"`
	DNSResolverMode        *string  `json:"dns_resolver_mode,omitempty"`
	Active                 *bool    `json:"active,omitempty"`
	Description            *string  `json:"description,omitempty"`
	Tags                   []string `json:"tags,omitempty"`
	CertExpiryNotification *bool    `json:"cert_expiry_notification,omitempty"`
	CertExpiryDays         *int     `json:"cert_expiry_days,omitempty"`
	IgnoreTLSError         *bool    `json:"ignore_tls_error,omitempty"`
}

// listMonitors returns all monitors for the authenticated user
func (h *APIHandler) listMonitors(e *core.RequestEvent) error {
	userID := e.Auth.Id

	records, err := h.app.FindRecordsByFilter(
		"monitors",
		"user = {:userId}",
		"-created",
		0,
		0,
		map[string]any{"userId": userID},
	)
	if err != nil {
		return e.InternalServerError("Failed to fetch monitors", err)
	}

	monitors := make([]MonitorResponse, 0, len(records))
	for _, record := range records {
		monitors = append(monitors, recordToResponse(record))
	}

	return e.JSON(http.StatusOK, map[string]interface{}{
		"monitors": monitors,
	})
}

// getRecentHeartbeats fetches the last N heartbeats for a monitor
func (h *APIHandler) getRecentHeartbeats(monitorID string, limit int) []HeartbeatSummary {
	records, err := h.app.FindRecordsByFilter(
		"monitor_heartbeats",
		"monitor = {:monitorId}",
		"-time",
		limit,
		0,
		map[string]any{"monitorId": monitorID},
	)
	if err != nil {
		return nil
	}

	heartbeats := make([]HeartbeatSummary, 0, len(records))
	for _, hb := range records {
		var t time.Time
		if ts := hb.GetDateTime("time"); !ts.IsZero() {
			t = ts.Time()
		}
		heartbeats = append(heartbeats, HeartbeatSummary{
			Status: hb.GetString("status"),
			Ping:   hb.GetInt("ping"),
			Time:   t,
		})
	}
	return heartbeats
}

// getMonitor returns a single monitor by ID
func (h *APIHandler) getMonitor(e *core.RequestEvent) error {
	id := e.Request.PathValue("id")
	if id == "" {
		return e.BadRequestError("Monitor ID is required", nil)
	}

	record, err := h.app.FindRecordById("monitors", id)
	if err != nil {
		return e.NotFoundError("Monitor not found", err)
	}

	// Verify ownership
	if record.GetString("user") != e.Auth.Id {
		return e.ForbiddenError("Access denied", nil)
	}

	resp := recordToResponse(record)
	resp.RecentHeartbeats = h.getRecentHeartbeats(id, 12)

	return e.JSON(http.StatusOK, resp)
}

// createMonitor creates a new monitor
func (h *APIHandler) createMonitor(e *core.RequestEvent) error {
	var req CreateMonitorRequest
	if err := json.NewDecoder(e.Request.Body).Decode(&req); err != nil {
		return e.BadRequestError("Invalid request body", err)
	}

	// Validate required fields
	if req.Name == "" || req.Type == "" {
		return e.BadRequestError("Name and type are required", nil)
	}

	// Set defaults
	if req.Interval == 0 {
		req.Interval = 60
	}
	if req.Timeout == 0 {
		req.Timeout = 30
	}
	if req.Retries == 0 {
		req.Retries = 1
	}

	// Get collection
	collection, err := h.app.FindCollectionByNameOrId("monitors")
	if err != nil {
		return e.InternalServerError("Failed to get collection", err)
	}

	// Create record
	record := core.NewRecord(collection)
	record.Set("name", req.Name)
	record.Set("type", req.Type)
	record.Set("url", req.URL)
	record.Set("hostname", req.Hostname)
	record.Set("port", req.Port)
	record.Set("method", req.Method)
	record.Set("headers", req.Headers)
	record.Set("body", req.Body)
	record.Set("interval", req.Interval)
	record.Set("timeout", req.Timeout)
	record.Set("retries", req.Retries)
	record.Set("retry_interval", req.RetryInterval)
	record.Set("max_redirects", req.MaxRedirects)
	record.Set("keyword", req.Keyword)
	record.Set("json_query", req.JSONQuery)
	record.Set("expected_value", req.ExpectedValue)
	record.Set("invert_keyword", req.InvertKeyword)
	record.Set("dns_resolve_server", req.DNSResolveServer)
	record.Set("dns_resolver_mode", req.DNSResolverMode)
	record.Set("status", string(monitor.StatusPending))
	record.Set("active", true)
	record.Set("user", e.Auth.Id)
	record.Set("description", req.Description)
	record.Set("tags", req.Tags)
	record.Set("cert_expiry_notification", req.CertExpiryNotification)
	record.Set("cert_expiry_days", req.CertExpiryDays)
	record.Set("ignore_tls_error", req.IgnoreTLSError)
	record.Set("uptime_stats", map[string]float64{})

	if err := h.app.Save(record); err != nil {
		return e.InternalServerError("Failed to create monitor", err)
	}

	// Add to scheduler
	h.scheduler.AddMonitor(record)

	// Run initial check synchronously so the monitor shows real status immediately
	if _, err := h.scheduler.RunManualCheck(record.Id); err != nil {
		log.Printf("[monitor-api] Initial check failed for %s: %v", record.Id, err)
	}

	// Re-fetch the updated record to get the new status
	updatedRecord, err := h.app.FindRecordById("monitors", record.Id)
	if err == nil {
		record = updatedRecord
	}

	return e.JSON(http.StatusCreated, recordToResponse(record))
}

// updateMonitor updates an existing monitor
func (h *APIHandler) updateMonitor(e *core.RequestEvent) error {
	id := e.Request.PathValue("id")
	if id == "" {
		return e.BadRequestError("Monitor ID is required", nil)
	}

	record, err := h.app.FindRecordById("monitors", id)
	if err != nil {
		return e.NotFoundError("Monitor not found", err)
	}

	// Verify ownership
	if record.GetString("user") != e.Auth.Id {
		return e.ForbiddenError("Access denied", nil)
	}

	var req UpdateMonitorRequest
	if err := json.NewDecoder(e.Request.Body).Decode(&req); err != nil {
		return e.BadRequestError("Invalid request body", err)
	}

	// Update fields
	if req.Name != nil {
		record.Set("name", *req.Name)
	}
	if req.URL != nil {
		record.Set("url", *req.URL)
	}
	if req.Hostname != nil {
		record.Set("hostname", *req.Hostname)
	}
	if req.Port != nil {
		record.Set("port", *req.Port)
	}
	if req.Method != nil {
		record.Set("method", *req.Method)
	}
	if req.Headers != nil {
		record.Set("headers", *req.Headers)
	}
	if req.Body != nil {
		record.Set("body", *req.Body)
	}
	if req.Interval != nil {
		record.Set("interval", *req.Interval)
	}
	if req.Timeout != nil {
		record.Set("timeout", *req.Timeout)
	}
	if req.Retries != nil {
		record.Set("retries", *req.Retries)
	}
	if req.RetryInterval != nil {
		record.Set("retry_interval", *req.RetryInterval)
	}
	if req.MaxRedirects != nil {
		record.Set("max_redirects", *req.MaxRedirects)
	}
	if req.Keyword != nil {
		record.Set("keyword", *req.Keyword)
	}
	if req.JSONQuery != nil {
		record.Set("json_query", *req.JSONQuery)
	}
	if req.ExpectedValue != nil {
		record.Set("expected_value", *req.ExpectedValue)
	}
	if req.InvertKeyword != nil {
		record.Set("invert_keyword", *req.InvertKeyword)
	}
	if req.DNSResolveServer != nil {
		record.Set("dns_resolve_server", *req.DNSResolveServer)
	}
	if req.DNSResolverMode != nil {
		record.Set("dns_resolver_mode", *req.DNSResolverMode)
	}
	if req.Active != nil {
		record.Set("active", *req.Active)
	}
	if req.Description != nil {
		record.Set("description", *req.Description)
	}
	if req.Tags != nil {
		record.Set("tags", req.Tags)
	}
	if req.CertExpiryNotification != nil {
		record.Set("cert_expiry_notification", *req.CertExpiryNotification)
	}
	if req.CertExpiryDays != nil {
		record.Set("cert_expiry_days", *req.CertExpiryDays)
	}
	if req.IgnoreTLSError != nil {
		record.Set("ignore_tls_error", *req.IgnoreTLSError)
	}

	if err := h.app.Save(record); err != nil {
		return e.InternalServerError("Failed to update monitor", err)
	}

	// Update scheduler
	h.scheduler.UpdateMonitor(record)

	return e.JSON(http.StatusOK, recordToResponse(record))
}

// deleteMonitor deletes a monitor
func (h *APIHandler) deleteMonitor(e *core.RequestEvent) error {
	id := e.Request.PathValue("id")
	if id == "" {
		return e.BadRequestError("Monitor ID is required", nil)
	}

	record, err := h.app.FindRecordById("monitors", id)
	if err != nil {
		return e.NotFoundError("Monitor not found", err)
	}

	// Verify ownership
	if record.GetString("user") != e.Auth.Id {
		return e.ForbiddenError("Access denied", nil)
	}

	// Remove from scheduler first
	h.scheduler.RemoveMonitor(id)

	if err := h.app.Delete(record); err != nil {
		return e.InternalServerError("Failed to delete monitor", err)
	}

	return e.JSON(http.StatusOK, map[string]string{"message": "Monitor deleted"})
}

// manualCheck runs a manual check for a monitor
func (h *APIHandler) manualCheck(e *core.RequestEvent) error {
	id := e.Request.PathValue("id")
	if id == "" {
		return e.BadRequestError("Monitor ID is required", nil)
	}

	record, err := h.app.FindRecordById("monitors", id)
	if err != nil {
		return e.NotFoundError("Monitor not found", err)
	}

	// Verify ownership
	if record.GetString("user") != e.Auth.Id {
		return e.ForbiddenError("Access denied", nil)
	}

	result, err := h.scheduler.RunManualCheck(id)
	if err != nil {
		return e.InternalServerError("Check failed", err)
	}

	response := map[string]interface{}{
		"status": result.Status,
		"ping":   result.Ping,
		"msg":    result.Msg,
	}

	if hbs, err := h.app.FindRecordsByFilter(
		"monitor_heartbeats",
		"monitor = {:monitorId}",
		"-time",
		1,
		0,
		dbx.Params{"monitorId": id},
	); err == nil && len(hbs) > 0 {
		response["heartbeat_id"] = hbs[0].Id
		response["time"] = hbs[0].GetDateTime("time").String()
	}

	return e.JSON(http.StatusOK, response)
}

// pauseMonitor pauses a monitor
func (h *APIHandler) pauseMonitor(e *core.RequestEvent) error {
	id := e.Request.PathValue("id")
	if id == "" {
		return e.BadRequestError("Monitor ID is required", nil)
	}

	record, err := h.app.FindRecordById("monitors", id)
	if err != nil {
		return e.NotFoundError("Monitor not found", err)
	}

	// Verify ownership
	if record.GetString("user") != e.Auth.Id {
		return e.ForbiddenError("Access denied", nil)
	}

	record.Set("active", false)
	record.Set("status", string(monitor.StatusPaused))

	if err := h.app.Save(record); err != nil {
		return e.InternalServerError("Failed to pause monitor", err)
	}

	h.scheduler.UpdateMonitor(record)

	return e.JSON(http.StatusOK, recordToResponse(record))
}

// resumeMonitor resumes a paused monitor
func (h *APIHandler) resumeMonitor(e *core.RequestEvent) error {
	id := e.Request.PathValue("id")
	if id == "" {
		return e.BadRequestError("Monitor ID is required", nil)
	}

	record, err := h.app.FindRecordById("monitors", id)
	if err != nil {
		return e.NotFoundError("Monitor not found", err)
	}

	// Verify ownership
	if record.GetString("user") != e.Auth.Id {
		return e.ForbiddenError("Access denied", nil)
	}

	record.Set("active", true)
	record.Set("status", string(monitor.StatusPending))

	if err := h.app.Save(record); err != nil {
		return e.InternalServerError("Failed to resume monitor", err)
	}

	h.scheduler.UpdateMonitor(record)

	// Run an immediate check so the monitor shows real status instead of pending
	if _, err := h.scheduler.RunManualCheck(record.Id); err != nil {
		log.Printf("[monitor-api] Resume check failed for %s: %v", record.Id, err)
	}

	// Re-fetch the updated record to get the new status
	updatedRecord, err := h.app.FindRecordById("monitors", record.Id)
	if err == nil {
		record = updatedRecord
	}

	return e.JSON(http.StatusOK, recordToResponse(record))
}

// getStats returns uptime statistics for a monitor
func (h *APIHandler) getStats(e *core.RequestEvent) error {
	id := e.Request.PathValue("id")
	if id == "" {
		return e.BadRequestError("Monitor ID is required", nil)
	}

	record, err := h.app.FindRecordById("monitors", id)
	if err != nil {
		return e.NotFoundError("Monitor not found", err)
	}

	// Verify ownership
	if record.GetString("user") != e.Auth.Id {
		return e.ForbiddenError("Access denied", nil)
	}

	stats24h, _ := h.scheduler.GetUptimeStats(id, 24)
	stats7d, _ := h.scheduler.GetUptimeStats(id, 168)
	stats30d, _ := h.scheduler.GetUptimeStats(id, 720)
	avg24h := 0.0
	if stats24h.Total > 0 {
		records, _ := h.app.FindRecordsByFilter(
			"monitor_heartbeats",
			"monitor = {:monitorId} && time >= {:since} && status = {:status}",
			"-time",
			0,
			0,
			dbx.Params{
				"monitorId": id,
				"since":     time.Now().Add(-24 * time.Hour).Format("2006-01-02 15:04:05"),
				"status":    string(monitor.StatusUp),
			},
		)
		if len(records) > 0 {
			totalPing := 0
			for _, record := range records {
				totalPing += record.GetInt("ping")
			}
			avg24h = float64(totalPing) / float64(len(records))
		}
	}

	return e.JSON(http.StatusOK, map[string]interface{}{
		"uptime_24h":         stats24h,
		"uptime_7d":          stats7d,
		"uptime_30d":         stats30d,
		"uptime_percent_24h": percent(stats24h),
		"uptime_percent_7d":  percent(stats7d),
		"uptime_percent_30d": percent(stats30d),
		"avg_ping_24h":       avg24h,
	})
}

// getHeartbeats returns recent heartbeats for a monitor
func (h *APIHandler) getHeartbeats(e *core.RequestEvent) error {
	id := e.Request.PathValue("id")
	if id == "" {
		return e.BadRequestError("Monitor ID is required", nil)
	}

	record, err := h.app.FindRecordById("monitors", id)
	if err != nil {
		return e.NotFoundError("Monitor not found", err)
	}

	// Verify ownership
	if record.GetString("user") != e.Auth.Id {
		return e.ForbiddenError("Access denied", nil)
	}

	// Get limit from query, default 100
	limit := 100

	records, err := h.app.FindRecordsByFilter(
		"monitor_heartbeats",
		"monitor = {:monitorId}",
		"-time",
		limit,
		0,
		map[string]any{
			"monitorId": id,
		},
	)
	if err != nil {
		return e.InternalServerError("Failed to fetch heartbeats", err)
	}

	heartbeats := make([]map[string]interface{}, 0, len(records))
	for _, hb := range records {
		heartbeats = append(heartbeats, map[string]interface{}{
			"id":          hb.Id,
			"status":      hb.GetString("status"),
			"ping":        hb.GetInt("ping"),
			"msg":         hb.GetString("msg"),
			"cert_expiry": hb.GetInt("cert_expiry"),
			"cert_valid":  hb.GetBool("cert_valid"),
			"time":        hb.Get("time"),
		})
	}

	return e.JSON(http.StatusOK, map[string]interface{}{
		"heartbeats": heartbeats,
	})
}

// recordToResponse converts a PocketBase record to MonitorResponse
func recordToResponse(record *core.Record) MonitorResponse {
	resp := MonitorResponse{
		ID:                     record.Id,
		Name:                   record.GetString("name"),
		Type:                   record.GetString("type"),
		URL:                    record.GetString("url"),
		Hostname:               record.GetString("hostname"),
		Port:                   record.GetInt("port"),
		Method:                 record.GetString("method"),
		Interval:               record.GetInt("interval"),
		Timeout:                record.GetInt("timeout"),
		Retries:                record.GetInt("retries"),
		Status:                 record.GetString("status"),
		Active:                 record.GetBool("active"),
		Description:            record.GetString("description"),
		Keyword:                record.GetString("keyword"),
		JSONQuery:              record.GetString("json_query"),
		ExpectedValue:          record.GetString("expected_value"),
		InvertKeyword:          record.GetBool("invert_keyword"),
		DNSResolveServer:       record.GetString("dns_resolve_server"),
		DNSResolverMode:        record.GetString("dns_resolver_mode"),
		CertExpiryNotification: record.GetBool("cert_expiry_notification"),
		CertExpiryDays:         record.GetInt("cert_expiry_days"),
		IgnoreTLSError:         record.GetBool("ignore_tls_error"),
		Created:                record.GetDateTime("created").Time(),
		Updated:                record.GetDateTime("updated").Time(),
	}

	if lc := record.GetDateTime("last_check"); !lc.IsZero() {
		t := lc.Time()
		resp.LastCheck = &t
	}

	if stats := record.Get("uptime_stats"); stats != nil {
		var s map[string]float64
		if raw, err := json.Marshal(stats); err == nil && json.Unmarshal(raw, &s) == nil {
			resp.UptimeStats = s
		}
	}

	// Handle tags
	if tags := record.Get("tags"); tags != nil {
		if t, ok := tags.([]string); ok {
			resp.Tags = t
		}
	}

	return resp
}

func percent(stats *monitor.UptimeStats) float64 {
	if stats == nil || stats.Total == 0 {
		return 0
	}
	return float64(stats.Up) / float64(stats.Total) * 100
}
