package chat

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"time"

	"gokin/internal/fileutil"
)

// HistoryEntry represents a saved history entry.
type HistoryEntry struct {
	Role      string    `json:"role"`
	Content   string    `json:"content"`
	Timestamp time.Time `json:"timestamp"`
}

// HistoryFile represents a saved session history.
type HistoryFile struct {
	SessionID string         `json:"session_id"`
	StartTime time.Time      `json:"start_time"`
	EndTime   time.Time      `json:"end_time"`
	Entries   []HistoryEntry `json:"entries"`
}

// HistoryManager manages session history persistence.
type HistoryManager struct {
	dataDir string
}

// NewHistoryManager creates a new history manager.
func NewHistoryManager() (*HistoryManager, error) {
	// Get data directory
	dataDir, err := getDataDir()
	if err != nil {
		return nil, err
	}

	// Create directory if it doesn't exist (0700: only owner can access session data)
	if err := os.MkdirAll(dataDir, 0700); err != nil {
		return nil, err
	}

	return &HistoryManager{
		dataDir: dataDir,
	}, nil
}

// Save saves a session history to disk.
// Save persists a degraded view of the session — only role+text per
// entry, no tool calls / responses. This file format predates the
// fuller SessionState used by SaveFull/LoadFull and is kept for
// backward-compatible reads of older session files.
//
// Deprecated: use SaveFull. Save loses tool interactions, so any
// session resumed from a Save'd file will look like the model never
// called any tools. Last in-tree user (the shutdown fallback in
// app/signals.go saveSessionHistory) was switched to SaveFull —
// kept around only for backward-compatible reads of older session
// files written by the legacy code path.
func (m *HistoryManager) Save(session *Session) error {
	history := session.GetHistory()

	file := HistoryFile{
		SessionID: session.ID,
		StartTime: session.StartTime,
		EndTime:   time.Now(),
		Entries:   make([]HistoryEntry, 0),
	}

	for _, content := range history {
		var text string
		for _, part := range content.Parts {
			if part.Text != "" {
				text += part.Text
			}
		}

		file.Entries = append(file.Entries, HistoryEntry{
			Role:      string(content.Role),
			Content:   text,
			Timestamp: time.Now(),
		})
	}

	// Marshal to JSON
	data, err := json.MarshalIndent(file, "", "  ")
	if err != nil {
		return err
	}

	// Write file (0600: only owner can read/write session data).
	// AtomicWrite (tmp + Sync + rename) so a SIGKILL mid-write doesn't
	// leave a half-flushed JSON that fails to load on next start.
	filename := filepath.Join(m.dataDir, session.ID+".json")
	return fileutil.AtomicWrite(filename, data, 0600)
}

// Load loads a session history from disk.
func (m *HistoryManager) Load(sessionID string) (*HistoryFile, error) {
	filename := filepath.Join(m.dataDir, sessionID+".json")

	data, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	var file HistoryFile
	if err := json.Unmarshal(data, &file); err != nil {
		return nil, err
	}

	return &file, nil
}

// List lists all saved sessions.
func (m *HistoryManager) List() ([]string, error) {
	entries, err := os.ReadDir(m.dataDir)
	if err != nil {
		return nil, err
	}

	var sessions []string
	for _, entry := range entries {
		if !entry.IsDir() && filepath.Ext(entry.Name()) == ".json" {
			sessions = append(sessions, entry.Name()[:len(entry.Name())-5])
		}
	}

	return sessions, nil
}

// Delete deletes a session history.
func (m *HistoryManager) Delete(sessionID string) error {
	filename := filepath.Join(m.dataDir, sessionID+".json")
	return os.Remove(filename)
}

// getDataDir returns the data directory for history storage.
func getDataDir() (string, error) {
	// Check XDG_DATA_HOME first
	if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" {
		return filepath.Join(xdgData, "gokin", "history"), nil
	}

	// Fall back to ~/.local/share
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(homeDir, ".local", "share", "gokin", "history"), nil
}

// getSessionsDir returns the data directory for full session storage.
func getSessionsDir() (string, error) {
	// Check XDG_DATA_HOME first
	if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" {
		return filepath.Join(xdgData, "gokin", "sessions"), nil
	}

	// Fall back to ~/.local/share
	homeDir, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(homeDir, ".local", "share", "gokin", "sessions"), nil
}

// SaveFull saves a complete session state including all content.
// Uses atomic write (tmp + rename) to prevent corruption on crash.
func (m *HistoryManager) SaveFull(session *Session) error {
	sessionsDir, err := getSessionsDir()
	if err != nil {
		return err
	}

	// Create directory if it doesn't exist (0700: only owner can access session data)
	if err := os.MkdirAll(sessionsDir, 0700); err != nil {
		return err
	}

	state := session.GetState()

	// Marshal to JSON
	data, err := json.MarshalIndent(state, "", "  ")
	if err != nil {
		return err
	}

	// Atomic write via fileutil.AtomicWrite (tmp + Sync + rename).
	// Pre-fix this used WriteFile + Rename without Sync, so a power loss
	// between WriteFile and Rename could leave the rename pointing at an
	// unflushed empty file.
	filename := filepath.Join(sessionsDir, session.ID+".json")
	return fileutil.AtomicWrite(filename, data, 0600)
}

// LoadFull loads a complete session state.
func (m *HistoryManager) LoadFull(sessionID string) (*SessionState, error) {
	sessionsDir, err := getSessionsDir()
	if err != nil {
		return nil, err
	}

	filename := filepath.Join(sessionsDir, sessionID+".json")

	data, err := os.ReadFile(filename)
	if err != nil {
		return nil, err
	}

	var state SessionState
	if err := json.Unmarshal(data, &state); err != nil {
		return nil, err
	}

	return &state, nil
}

// ListSessions returns information about all saved sessions.
func (m *HistoryManager) ListSessions() ([]SessionInfo, error) {
	sessionsDir, err := getSessionsDir()
	if err != nil {
		return nil, err
	}

	// Create directory if it doesn't exist (0700: only owner can access session data)
	if err := os.MkdirAll(sessionsDir, 0700); err != nil {
		return nil, err
	}

	entries, err := os.ReadDir(sessionsDir)
	if err != nil {
		return nil, err
	}

	var sessions []SessionInfo
	for _, entry := range entries {
		if entry.IsDir() || filepath.Ext(entry.Name()) != ".json" {
			continue
		}

		sessionID := strings.TrimSuffix(entry.Name(), ".json")
		state, err := m.LoadFull(sessionID)
		if err != nil {
			continue // Skip invalid files
		}

		sessions = append(sessions, SessionInfo{
			ID:           state.ID,
			StartTime:    state.StartTime,
			LastActive:   state.LastActive,
			Summary:      state.Summary,
			MessageCount: len(state.History),
			WorkDir:      state.WorkDir,
		})
	}

	return sessions, nil
}

// DeleteSession deletes a saved session.
func (m *HistoryManager) DeleteSession(sessionID string) error {
	sessionsDir, err := getSessionsDir()
	if err != nil {
		return err
	}

	filename := filepath.Join(sessionsDir, sessionID+".json")
	return os.Remove(filename)
}
