package updates

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/oklog/ulid/v2"
	"github.com/rs/zerolog/log"
)

// UpdateAction represents the type of update action
type UpdateAction string

const (
	ActionUpdate   UpdateAction = "update"
	ActionRollback UpdateAction = "rollback"
)

// UpdateStatus represents the outcome of an update
type UpdateStatusType string

const (
	StatusInProgress UpdateStatusType = "in_progress"
	StatusSuccess    UpdateStatusType = "success"
	StatusFailed     UpdateStatusType = "failed"
	StatusRolledBack UpdateStatusType = "rolled_back"
	StatusCancelled  UpdateStatusType = "cancelled"
)

// InitiatedBy represents who triggered the update
type InitiatedBy string

const (
	InitiatedByUser InitiatedBy = "user"
	InitiatedByAuto InitiatedBy = "auto"
	InitiatedByAPI  InitiatedBy = "api"
)

// InitiatedVia represents the interface used to trigger the update
type InitiatedVia string

const (
	InitiatedViaUI      InitiatedVia = "ui"
	InitiatedViaCLI     InitiatedVia = "cli"
	InitiatedViaScript  InitiatedVia = "script"
	InitiatedViaWebhook InitiatedVia = "webhook"
)

// UpdateHistoryEntry represents a single update event
type UpdateHistoryEntry struct {
	EventID        string              `json:"event_id"`
	Timestamp      time.Time           `json:"timestamp"`
	Action         UpdateAction        `json:"action"`
	Channel        string              `json:"channel"`
	VersionFrom    string              `json:"version_from"`
	VersionTo      string              `json:"version_to"`
	DeploymentType string              `json:"deployment_type"`
	InitiatedBy    InitiatedBy         `json:"initiated_by"`
	InitiatedVia   InitiatedVia        `json:"initiated_via"`
	Status         UpdateStatusType    `json:"status"`
	DurationMs     int64               `json:"duration_ms"`
	BackupPath     string              `json:"backup_path,omitempty"`
	LogPath        string              `json:"log_path,omitempty"`
	Error          *UpdateError        `json:"error,omitempty"`
	DownloadBytes  int64               `json:"download_bytes,omitempty"`
	RelatedEventID string              `json:"related_event_id,omitempty"`
	Notes          string              `json:"notes,omitempty"`
}

// UpdateError represents error information
type UpdateError struct {
	Message string `json:"message"`
	Code    string `json:"code,omitempty"`
	Details string `json:"details,omitempty"`
}

// HistoryFilter represents filters for querying update history
type HistoryFilter struct {
	Status       UpdateStatusType
	Action       UpdateAction
	DeploymentType string
	Limit        int
}

// UpdateHistory manages the update history log
type UpdateHistory struct {
	logPath  string
	mu       sync.RWMutex
	cache    []UpdateHistoryEntry
	maxCache int
}

// NewUpdateHistory creates a new update history manager
func NewUpdateHistory(dataDir string) (*UpdateHistory, error) {
	if dataDir == "" {
		dataDir = "/var/lib/pulse"
	}

	// Ensure directory exists
	if err := os.MkdirAll(dataDir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create data directory: %w", err)
	}

	logPath := filepath.Join(dataDir, "update-history.jsonl")

	h := &UpdateHistory{
		logPath:  logPath,
		cache:    make([]UpdateHistoryEntry, 0, 100),
		maxCache: 100,
	}

	// Load existing entries into cache
	if err := h.loadCache(); err != nil {
		log.Warn().Err(err).Msg("Failed to load update history cache")
	}

	return h, nil
}

// CreateEntry creates a new update history entry and returns the event ID
func (h *UpdateHistory) CreateEntry(ctx context.Context, entry UpdateHistoryEntry) (string, error) {
	h.mu.Lock()
	defer h.mu.Unlock()

	// Generate event ID if not provided
	if entry.EventID == "" {
		entry.EventID = ulid.Make().String()
	}

	// Set timestamp if not provided
	if entry.Timestamp.IsZero() {
		entry.Timestamp = time.Now()
	}

	// Write to file
	if err := h.appendToFile(entry); err != nil {
		return "", fmt.Errorf("failed to write to history file: %w", err)
	}

	// Add to cache
	h.addToCache(entry)

	log.Info().
		Str("event_id", entry.EventID).
		Str("action", string(entry.Action)).
		Str("version_from", entry.VersionFrom).
		Str("version_to", entry.VersionTo).
		Msg("Created update history entry")

	return entry.EventID, nil
}

// UpdateEntry updates an existing entry (used to update status after completion)
func (h *UpdateHistory) UpdateEntry(ctx context.Context, eventID string, updateFn func(*UpdateHistoryEntry) error) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	// Find entry in cache
	var entry *UpdateHistoryEntry
	for i := range h.cache {
		if h.cache[i].EventID == eventID {
			entry = &h.cache[i]
			break
		}
	}

	if entry == nil {
		return fmt.Errorf("entry not found: %s", eventID)
	}

	// Apply update
	if err := updateFn(entry); err != nil {
		return err
	}

	// Rewrite the entire file (JSONL doesn't support in-place updates)
	// For small files this is acceptable; for large files we'd need a database
	if err := h.rewriteFile(); err != nil {
		return fmt.Errorf("failed to update history file: %w", err)
	}

	log.Info().
		Str("event_id", eventID).
		Str("status", string(entry.Status)).
		Msg("Updated update history entry")

	return nil
}

// GetEntry retrieves a specific entry by ID
func (h *UpdateHistory) GetEntry(eventID string) (*UpdateHistoryEntry, error) {
	h.mu.RLock()
	defer h.mu.RUnlock()

	for i := range h.cache {
		if h.cache[i].EventID == eventID {
			return &h.cache[i], nil
		}
	}

	return nil, fmt.Errorf("entry not found: %s", eventID)
}

// ListEntries returns entries matching the filter
func (h *UpdateHistory) ListEntries(filter HistoryFilter) []UpdateHistoryEntry {
	h.mu.RLock()
	defer h.mu.RUnlock()

	result := make([]UpdateHistoryEntry, 0)

	for i := len(h.cache) - 1; i >= 0; i-- {
		entry := h.cache[i]

		// Apply filters
		if filter.Status != "" && entry.Status != filter.Status {
			continue
		}
		if filter.Action != "" && entry.Action != filter.Action {
			continue
		}
		if filter.DeploymentType != "" && entry.DeploymentType != filter.DeploymentType {
			continue
		}

		result = append(result, entry)

		// Check limit
		if filter.Limit > 0 && len(result) >= filter.Limit {
			break
		}
	}

	return result
}

// GetLatestSuccessful returns the most recent successful update
func (h *UpdateHistory) GetLatestSuccessful() (*UpdateHistoryEntry, error) {
	h.mu.RLock()
	defer h.mu.RUnlock()

	for i := len(h.cache) - 1; i >= 0; i-- {
		if h.cache[i].Status == StatusSuccess {
			return &h.cache[i], nil
		}
	}

	return nil, fmt.Errorf("no successful updates found")
}

// loadCache loads entries from the JSONL file into memory
func (h *UpdateHistory) loadCache() error {
	file, err := os.Open(h.logPath)
	if err != nil {
		if os.IsNotExist(err) {
			// File doesn't exist yet, that's OK
			return nil
		}
		return err
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	entries := make([]UpdateHistoryEntry, 0)

	for scanner.Scan() {
		line := scanner.Text()
		if line == "" {
			continue
		}

		var entry UpdateHistoryEntry
		if err := json.Unmarshal([]byte(line), &entry); err != nil {
			log.Warn().Err(err).Str("line", line).Msg("Failed to parse history entry")
			continue
		}

		entries = append(entries, entry)
	}

	if err := scanner.Err(); err != nil {
		return err
	}

	// Keep only the most recent entries
	if len(entries) > h.maxCache {
		entries = entries[len(entries)-h.maxCache:]
	}

	h.cache = entries

	log.Info().Int("count", len(h.cache)).Msg("Loaded update history cache")

	return nil
}

// appendToFile appends an entry to the JSONL file
func (h *UpdateHistory) appendToFile(entry UpdateHistoryEntry) error {
	file, err := os.OpenFile(h.logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	defer file.Close()

	data, err := json.Marshal(entry)
	if err != nil {
		return err
	}

	if _, err := file.Write(append(data, '\n')); err != nil {
		return err
	}

	// Sync to disk
	return file.Sync()
}

// rewriteFile rewrites the entire history file from cache
func (h *UpdateHistory) rewriteFile() error {
	// Write to temp file first
	tempPath := h.logPath + ".tmp"
	file, err := os.Create(tempPath)
	if err != nil {
		return err
	}

	for _, entry := range h.cache {
		data, err := json.Marshal(entry)
		if err != nil {
			file.Close()
			os.Remove(tempPath)
			return err
		}

		if _, err := file.Write(append(data, '\n')); err != nil {
			file.Close()
			os.Remove(tempPath)
			return err
		}
	}

	if err := file.Sync(); err != nil {
		file.Close()
		os.Remove(tempPath)
		return err
	}

	file.Close()

	// Atomic rename
	return os.Rename(tempPath, h.logPath)
}

// addToCache adds an entry to the in-memory cache
func (h *UpdateHistory) addToCache(entry UpdateHistoryEntry) {
	h.cache = append(h.cache, entry)

	// Trim cache if it exceeds max size
	if len(h.cache) > h.maxCache {
		h.cache = h.cache[len(h.cache)-h.maxCache:]
	}
}
