package handlers

import (
	"errors"
	"fmt"
	"html/template"
	"log/slog"
	"net/http"
	"os"
	"path/filepath"
	stdsync "sync"
	"time"

	qfassets "git.mchus.pro/mchus/quoteforge"
	"git.mchus.pro/mchus/quoteforge/internal/db"
	"git.mchus.pro/mchus/quoteforge/internal/localdb"
	"git.mchus.pro/mchus/quoteforge/internal/services/sync"
	"github.com/gin-gonic/gin"
)

// SyncHandler handles sync API endpoints
type SyncHandler struct {
	localDB           *localdb.LocalDB
	syncService       *sync.Service
	connMgr           *db.ConnectionManager
	autoSyncInterval  time.Duration
	onlineGraceFactor float64
	tmpl              *template.Template
	readinessMu       stdsync.Mutex
	readinessCached   *sync.SyncReadiness
	readinessCachedAt time.Time
}

// NewSyncHandler creates a new sync handler
func NewSyncHandler(localDB *localdb.LocalDB, syncService *sync.Service, connMgr *db.ConnectionManager, templatesPath string, autoSyncInterval time.Duration) (*SyncHandler, error) {
	// Load sync_status partial template
	partialPath := filepath.Join(templatesPath, "partials", "sync_status.html")
	var tmpl *template.Template
	var err error
	if stat, statErr := os.Stat(templatesPath); statErr == nil && stat.IsDir() {
		tmpl, err = template.ParseFiles(partialPath)
	} else {
		tmpl, err = template.ParseFS(qfassets.TemplatesFS, "web/templates/partials/sync_status.html")
	}
	if err != nil {
		return nil, err
	}

	return &SyncHandler{
		localDB:           localDB,
		syncService:       syncService,
		connMgr:           connMgr,
		autoSyncInterval:  autoSyncInterval,
		onlineGraceFactor: 1.10,
		tmpl:              tmpl,
	}, nil
}

// SyncStatusResponse represents the sync status
type SyncStatusResponse struct {
	LastComponentSync *time.Time          `json:"last_component_sync"`
	LastPricelistSync *time.Time          `json:"last_pricelist_sync"`
	IsOnline          bool                `json:"is_online"`
	ComponentsCount   int64               `json:"components_count"`
	PricelistsCount   int64               `json:"pricelists_count"`
	ServerPricelists  int                 `json:"server_pricelists"`
	NeedComponentSync bool                `json:"need_component_sync"`
	NeedPricelistSync bool                `json:"need_pricelist_sync"`
	Readiness         *sync.SyncReadiness `json:"readiness,omitempty"`
}

type SyncReadinessResponse struct {
	Status                string     `json:"status"`
	Blocked               bool       `json:"blocked"`
	ReasonCode            string     `json:"reason_code,omitempty"`
	ReasonText            string     `json:"reason_text,omitempty"`
	RequiredMinAppVersion *string    `json:"required_min_app_version,omitempty"`
	LastCheckedAt         *time.Time `json:"last_checked_at,omitempty"`
}

// GetStatus returns current sync status
// GET /api/sync/status
func (h *SyncHandler) GetStatus(c *gin.Context) {
	// Check online status by pinging MariaDB
	isOnline := h.checkOnline()

	// Get sync times
	lastComponentSync := h.localDB.GetComponentSyncTime()
	lastPricelistSync := h.localDB.GetLastSyncTime()

	// Get counts
	componentsCount := h.localDB.CountLocalComponents()
	pricelistsCount := h.localDB.CountLocalPricelists()

	// Get server pricelist count if online
	serverPricelists := 0
	needPricelistSync := false
	if isOnline {
		status, err := h.syncService.GetStatus()
		if err == nil {
			serverPricelists = status.ServerPricelists
			needPricelistSync = status.NeedsSync
		}
	}

	// Check if component sync is needed (older than 24 hours)
	needComponentSync := h.localDB.NeedComponentSync(24)
	readiness := h.getReadinessCached(10 * time.Second)

	c.JSON(http.StatusOK, SyncStatusResponse{
		LastComponentSync: lastComponentSync,
		LastPricelistSync: lastPricelistSync,
		IsOnline:          isOnline,
		ComponentsCount:   componentsCount,
		PricelistsCount:   pricelistsCount,
		ServerPricelists:  serverPricelists,
		NeedComponentSync: needComponentSync,
		NeedPricelistSync: needPricelistSync,
		Readiness:         readiness,
	})
}

// GetReadiness returns sync readiness guard status.
// GET /api/sync/readiness
func (h *SyncHandler) GetReadiness(c *gin.Context) {
	readiness, err := h.syncService.GetReadiness()
	if err != nil && readiness == nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
		return
	}
	if readiness == nil {
		c.JSON(http.StatusOK, SyncReadinessResponse{Status: sync.ReadinessUnknown, Blocked: false})
		return
	}
	c.JSON(http.StatusOK, SyncReadinessResponse{
		Status:                readiness.Status,
		Blocked:               readiness.Blocked,
		ReasonCode:            readiness.ReasonCode,
		ReasonText:            readiness.ReasonText,
		RequiredMinAppVersion: readiness.RequiredMinAppVersion,
		LastCheckedAt:         readiness.LastCheckedAt,
	})
}

func (h *SyncHandler) ensureSyncReadiness(c *gin.Context) bool {
	readiness, err := h.syncService.EnsureReadinessForSync()
	if err == nil {
		return true
	}

	blocked := &sync.SyncBlockedError{}
	if errors.As(err, &blocked) {
		c.JSON(http.StatusLocked, gin.H{
			"success":                  false,
			"error":                    blocked.Error(),
			"reason_code":              blocked.Readiness.ReasonCode,
			"reason_text":              blocked.Readiness.ReasonText,
			"required_min_app_version": blocked.Readiness.RequiredMinAppVersion,
			"status":                   blocked.Readiness.Status,
			"blocked":                  true,
			"last_checked_at":          blocked.Readiness.LastCheckedAt,
		})
		return false
	}

	c.JSON(http.StatusInternalServerError, gin.H{
		"success": false,
		"error":   err.Error(),
	})
	_ = readiness
	return false
}

// SyncResultResponse represents sync operation result
type SyncResultResponse struct {
	Success  bool   `json:"success"`
	Message  string `json:"message"`
	Synced   int    `json:"synced"`
	Duration string `json:"duration"`
}

// SyncComponents syncs components from MariaDB to local SQLite
// POST /api/sync/components
func (h *SyncHandler) SyncComponents(c *gin.Context) {
	if !h.ensureSyncReadiness(c) {
		return
	}

	// Get database connection from ConnectionManager
	mariaDB, err := h.connMgr.GetDB()
	if err != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{
			"success": false,
			"error":   "Database connection failed: " + err.Error(),
		})
		return
	}

	result, err := h.localDB.SyncComponents(mariaDB)
	if err != nil {
		slog.Error("component sync failed", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, SyncResultResponse{
		Success:  true,
		Message:  "Components synced successfully",
		Synced:   result.TotalSynced,
		Duration: result.Duration.String(),
	})
}

// SyncPricelists syncs pricelists from MariaDB to local SQLite
// POST /api/sync/pricelists
func (h *SyncHandler) SyncPricelists(c *gin.Context) {
	if !h.ensureSyncReadiness(c) {
		return
	}

	startTime := time.Now()
	synced, err := h.syncService.SyncPricelists()
	if err != nil {
		slog.Error("pricelist sync failed", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, SyncResultResponse{
		Success:  true,
		Message:  "Pricelists synced successfully",
		Synced:   synced,
		Duration: time.Since(startTime).String(),
	})
	h.syncService.RecordSyncHeartbeat()
}

// SyncAllResponse represents result of full sync
type SyncAllResponse struct {
	Success                bool   `json:"success"`
	Message                string `json:"message"`
	PendingPushed          int    `json:"pending_pushed"`
	ComponentsSynced       int    `json:"components_synced"`
	PricelistsSynced       int    `json:"pricelists_synced"`
	ProjectsImported       int    `json:"projects_imported"`
	ProjectsUpdated        int    `json:"projects_updated"`
	ProjectsSkipped        int    `json:"projects_skipped"`
	ConfigurationsImported int    `json:"configurations_imported"`
	ConfigurationsUpdated  int    `json:"configurations_updated"`
	ConfigurationsSkipped  int    `json:"configurations_skipped"`
	Duration               string `json:"duration"`
}

// SyncAll performs full bidirectional sync:
// - push pending local changes (projects/configurations) to server
// - pull components, pricelists, projects, and configurations from server
// POST /api/sync/all
func (h *SyncHandler) SyncAll(c *gin.Context) {
	if !h.ensureSyncReadiness(c) {
		return
	}

	startTime := time.Now()
	var pendingPushed, componentsSynced, pricelistsSynced int

	// Push local pending changes first (projects/configurations)
	pendingPushed, err := h.syncService.PushPendingChanges()
	if err != nil {
		slog.Error("pending push failed during full sync", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   "Pending changes push failed: " + err.Error(),
		})
		return
	}

	// Sync components
	mariaDB, err := h.connMgr.GetDB()
	if err != nil {
		c.JSON(http.StatusServiceUnavailable, gin.H{
			"success": false,
			"error":   "Database connection failed: " + err.Error(),
		})
		return
	}

	compResult, err := h.localDB.SyncComponents(mariaDB)
	if err != nil {
		slog.Error("component sync failed during full sync", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   "Component sync failed: " + err.Error(),
		})
		return
	}
	componentsSynced = compResult.TotalSynced

	// Sync pricelists
	pricelistsSynced, err = h.syncService.SyncPricelists()
	if err != nil {
		slog.Error("pricelist sync failed during full sync", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"success":           false,
			"error":             "Pricelist sync failed: " + err.Error(),
			"pending_pushed":    pendingPushed,
			"components_synced": componentsSynced,
		})
		return
	}

	projectsResult, err := h.syncService.ImportProjectsToLocal()
	if err != nil {
		slog.Error("project import failed during full sync", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"success":           false,
			"error":             "Project import failed: " + err.Error(),
			"pending_pushed":    pendingPushed,
			"components_synced": componentsSynced,
			"pricelists_synced": pricelistsSynced,
		})
		return
	}

	configsResult, err := h.syncService.ImportConfigurationsToLocal()
	if err != nil {
		slog.Error("configuration import failed during full sync", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"success":           false,
			"error":             "Configuration import failed: " + err.Error(),
			"pending_pushed":    pendingPushed,
			"components_synced": componentsSynced,
			"pricelists_synced": pricelistsSynced,
			"projects_imported": projectsResult.Imported,
			"projects_updated":  projectsResult.Updated,
			"projects_skipped":  projectsResult.Skipped,
		})
		return
	}

	c.JSON(http.StatusOK, SyncAllResponse{
		Success:                true,
		Message:                "Full sync completed successfully",
		PendingPushed:          pendingPushed,
		ComponentsSynced:       componentsSynced,
		PricelistsSynced:       pricelistsSynced,
		ProjectsImported:       projectsResult.Imported,
		ProjectsUpdated:        projectsResult.Updated,
		ProjectsSkipped:        projectsResult.Skipped,
		ConfigurationsImported: configsResult.Imported,
		ConfigurationsUpdated:  configsResult.Updated,
		ConfigurationsSkipped:  configsResult.Skipped,
		Duration:               time.Since(startTime).String(),
	})
	h.syncService.RecordSyncHeartbeat()
}

// checkOnline checks if MariaDB is accessible
func (h *SyncHandler) checkOnline() bool {
	return h.connMgr.IsOnline()
}

// PushPendingChanges pushes all pending changes to the server
// POST /api/sync/push
func (h *SyncHandler) PushPendingChanges(c *gin.Context) {
	if !h.ensureSyncReadiness(c) {
		return
	}

	startTime := time.Now()
	pushed, err := h.syncService.PushPendingChanges()
	if err != nil {
		slog.Error("push pending changes failed", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, SyncResultResponse{
		Success:  true,
		Message:  "Pending changes pushed successfully",
		Synced:   pushed,
		Duration: time.Since(startTime).String(),
	})
	h.syncService.RecordSyncHeartbeat()
}

// GetPendingCount returns the number of pending changes
// GET /api/sync/pending/count
func (h *SyncHandler) GetPendingCount(c *gin.Context) {
	count := h.localDB.GetPendingCount()
	c.JSON(http.StatusOK, gin.H{
		"count": count,
	})
}

// GetPendingChanges returns all pending changes
// GET /api/sync/pending
func (h *SyncHandler) GetPendingChanges(c *gin.Context) {
	changes, err := h.localDB.GetPendingChanges()
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"changes": changes,
	})
}

// RepairPendingChanges attempts to repair errored pending changes
// POST /api/sync/repair
func (h *SyncHandler) RepairPendingChanges(c *gin.Context) {
	repaired, remainingErrors, err := h.localDB.RepairPendingChanges()
	if err != nil {
		slog.Error("repair pending changes failed", "error", err)
		c.JSON(http.StatusInternalServerError, gin.H{
			"success": false,
			"error":   err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"success":          true,
		"repaired":         repaired,
		"remaining_errors": remainingErrors,
	})
}

// SyncInfoResponse represents sync information for the modal
type SyncInfoResponse struct {
	// Connection
	DBHost string `json:"db_host"`
	DBUser string `json:"db_user"`
	DBName string `json:"db_name"`

	// Status
	IsOnline   bool       `json:"is_online"`
	LastSyncAt *time.Time `json:"last_sync_at"`

	// Statistics
	LotCount     int64 `json:"lot_count"`
	LotLogCount  int64 `json:"lot_log_count"`
	ConfigCount  int64 `json:"config_count"`
	ProjectCount int64 `json:"project_count"`

	// Pending changes
	PendingChanges []localdb.PendingChange `json:"pending_changes"`

	// Errors
	ErrorCount int         `json:"error_count"`
	Errors     []SyncError `json:"errors,omitempty"`

	// Readiness guard
	Readiness *sync.SyncReadiness `json:"readiness,omitempty"`
}

type SyncUsersStatusResponse struct {
	IsOnline                bool                  `json:"is_online"`
	AutoSyncIntervalSeconds int64                 `json:"auto_sync_interval_seconds"`
	OnlineThresholdSeconds  int64                 `json:"online_threshold_seconds"`
	GeneratedAt             time.Time             `json:"generated_at"`
	Users                   []sync.UserSyncStatus `json:"users"`
}

// SyncError represents a sync error
type SyncError struct {
	Timestamp time.Time `json:"timestamp"`
	Message   string    `json:"message"`
}

// GetInfo returns sync information for modal
// GET /api/sync/info
func (h *SyncHandler) GetInfo(c *gin.Context) {
	// Check online status by pinging MariaDB
	isOnline := h.checkOnline()

	// Get DB connection info
	var dbHost, dbUser, dbName string
	if settings, err := h.localDB.GetSettings(); err == nil {
		dbHost = settings.Host + ":" + fmt.Sprintf("%d", settings.Port)
		dbUser = settings.User
		dbName = settings.Database
	}

	// Get sync times
	lastPricelistSync := h.localDB.GetLastSyncTime()

	// Get MariaDB counts (if online)
	var lotCount, lotLogCount int64
	if isOnline {
		if mariaDB, err := h.connMgr.GetDB(); err == nil {
			mariaDB.Table("lot").Count(&lotCount)
			mariaDB.Table("lot_log").Count(&lotLogCount)
		}
	}

	// Get local counts
	configCount := h.localDB.CountConfigurations()
	projectCount := h.localDB.CountProjects()

	// Get error count (only changes with LastError != "")
	errorCount := int(h.localDB.CountErroredChanges())

	// Get pending changes
	changes, err := h.localDB.GetPendingChanges()
	if err != nil {
		slog.Error("failed to get pending changes for sync info", "error", err)
		changes = []localdb.PendingChange{}
	}

	var syncErrors []SyncError
	for _, change := range changes {
		if change.LastError != "" {
			syncErrors = append(syncErrors, SyncError{
				Timestamp: change.CreatedAt,
				Message:   change.LastError,
			})
		}
	}

	// Limit to last 10 errors
	if len(syncErrors) > 10 {
		syncErrors = syncErrors[:10]
	}

	readiness := h.getReadinessCached(10 * time.Second)

	c.JSON(http.StatusOK, SyncInfoResponse{
		DBHost:         dbHost,
		DBUser:         dbUser,
		DBName:         dbName,
		IsOnline:       isOnline,
		LastSyncAt:     lastPricelistSync,
		LotCount:       lotCount,
		LotLogCount:    lotLogCount,
		ConfigCount:    configCount,
		ProjectCount:   projectCount,
		PendingChanges: changes,
		ErrorCount:     errorCount,
		Errors:         syncErrors,
		Readiness:      readiness,
	})
}

// GetUsersStatus returns last sync timestamps for users with sync heartbeats.
// GET /api/sync/users-status
func (h *SyncHandler) GetUsersStatus(c *gin.Context) {
	threshold := time.Duration(float64(h.autoSyncInterval) * h.onlineGraceFactor)
	isOnline := h.checkOnline()

	if !isOnline {
		c.JSON(http.StatusOK, SyncUsersStatusResponse{
			IsOnline:                false,
			AutoSyncIntervalSeconds: int64(h.autoSyncInterval.Seconds()),
			OnlineThresholdSeconds:  int64(threshold.Seconds()),
			GeneratedAt:             time.Now().UTC(),
			Users:                   []sync.UserSyncStatus{},
		})
		return
	}

	// Keep current client heartbeat fresh so app version is available in the table.
	h.syncService.RecordSyncHeartbeat()

	users, err := h.syncService.ListUserSyncStatuses(threshold)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{
			"error": err.Error(),
		})
		return
	}

	c.JSON(http.StatusOK, SyncUsersStatusResponse{
		IsOnline:                true,
		AutoSyncIntervalSeconds: int64(h.autoSyncInterval.Seconds()),
		OnlineThresholdSeconds:  int64(threshold.Seconds()),
		GeneratedAt:             time.Now().UTC(),
		Users:                   users,
	})
}

// SyncStatusPartial renders the sync status partial for htmx
// GET /partials/sync-status
func (h *SyncHandler) SyncStatusPartial(c *gin.Context) {
	// Check online status from middleware
	isOfflineValue, exists := c.Get("is_offline")
	isOffline := false
	if exists {
		isOffline = isOfflineValue.(bool)
	} else {
		// Fallback: check directly if middleware didn't set it
		isOffline = !h.checkOnline()
		slog.Warn("is_offline not found in context, checking directly")
	}

	// Get pending count
	pendingCount := h.localDB.GetPendingCount()
	readiness := h.getReadinessCached(10 * time.Second)
	isBlocked := readiness != nil && readiness.Blocked

	slog.Debug("rendering sync status", "is_offline", isOffline, "pending_count", pendingCount, "sync_blocked", isBlocked)

	data := gin.H{
		"IsOffline":    isOffline,
		"PendingCount": pendingCount,
		"IsBlocked":    isBlocked,
		"BlockedReason": func() string {
			if readiness == nil {
				return ""
			}
			return readiness.ReasonText
		}(),
	}

	c.Header("Content-Type", "text/html; charset=utf-8")
	if err := h.tmpl.ExecuteTemplate(c.Writer, "sync_status", data); err != nil {
		slog.Error("failed to render sync_status template", "error", err)
		c.String(http.StatusInternalServerError, "Template error: "+err.Error())
	}
}

func (h *SyncHandler) getReadinessCached(maxAge time.Duration) *sync.SyncReadiness {
	h.readinessMu.Lock()
	if h.readinessCached != nil && time.Since(h.readinessCachedAt) < maxAge {
		cached := *h.readinessCached
		h.readinessMu.Unlock()
		return &cached
	}
	h.readinessMu.Unlock()

	readiness, err := h.syncService.GetReadiness()
	if err != nil && readiness == nil {
		return nil
	}

	h.readinessMu.Lock()
	h.readinessCached = readiness
	h.readinessCachedAt = time.Now()
	h.readinessMu.Unlock()
	return readiness
}
