// Package emergency is the Phase 15 panic-button implementation. Activation
// pauses all torrents, disables autobrr filters, silences notifications,
// and records a restoration snapshot the operator can later replay.
package emergency

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"sync"
	"time"

	"github.com/rs/zerolog"

	"github.com/operator/command-center/internal/integrations/qbit"
)

// State is the typed shape returned by /api/emergency/state.
type State struct {
	Active            bool   `json:"active"`
	ActivatedAt       int64  `json:"activated_at,omitempty"`
	SilenceUntil      int64  `json:"silence_until,omitempty"`
	SnapshotPath      string `json:"snapshot_path,omitempty"`
	PausedHashCount   int    `json:"paused_hash_count,omitempty"`
}

// ClientsAccessor is the narrow read interface needed from the torrent
// client registry.
type ClientsAccessor interface {
	IDs() []string
	Get(id string) (qbit.TorrentClient, bool)
}

// Manager owns the active state. Construct one; call Activate / Deactivate.
type Manager struct {
	db        *sql.DB
	clients   ClientsAccessor
	logger    zerolog.Logger
	mu        sync.RWMutex
	state     State
	snapshot  snapshotPayload
}

// NewManager constructs a Manager.
func NewManager(db *sql.DB, clients ClientsAccessor, logger zerolog.Logger) *Manager {
	return &Manager{
		db:      db,
		clients: clients,
		logger:  logger.With().Str("component", "emergency").Logger(),
	}
}

type snapshotPayload struct {
	PausedByClient map[string][]string `json:"paused_by_client"`
	ActivatedAt    int64               `json:"activated_at"`
}

// Activate pauses all torrents in all clients and writes a snapshot.
// silenceDuration controls how long notifications are silenced; the rules
// engine reads emergency state when deciding whether to dispatch.
func (m *Manager) Activate(ctx context.Context, silenceDuration time.Duration) (State, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if m.state.Active {
		return m.state, errors.New("emergency: already active")
	}

	snap := snapshotPayload{
		PausedByClient: map[string][]string{},
		ActivatedAt:    time.Now().Unix(),
	}
	var paused int
	if m.clients != nil {
		for _, id := range m.clients.IDs() {
			c, ok := m.clients.Get(id)
			if !ok {
				continue
			}
			list, err := c.List(ctx, qbit.ListFilter{State: "downloading"})
			if err != nil {
				m.logger.Warn().Err(err).Str("client", id).Msg("list torrents before pause failed")
				continue
			}
			list2, _ := c.List(ctx, qbit.ListFilter{State: "uploading"})
			list = append(list, list2...)
			hashes := make([]string, 0, len(list))
			for _, t := range list {
				hashes = append(hashes, t.Hash)
			}
			snap.PausedByClient[id] = hashes
			if len(hashes) > 0 {
				_ = c.Pause(ctx, hashes)
				paused += len(hashes)
			}
		}
	}

	// Persist the snapshot to audit_log for restoration.
	details, _ := json.Marshal(snap)
	_, _ = m.db.ExecContext(ctx, `
		INSERT INTO audit_log(timestamp, actor, action, target_type, target_id, details_json)
		VALUES (?, 'operator', 'emergency_activate', 'emergency', 'state', ?)
	`, time.Now().Unix(), string(details))

	silenceUntil := time.Now().Add(silenceDuration).Unix()
	m.state = State{
		Active:          true,
		ActivatedAt:     snap.ActivatedAt,
		SilenceUntil:    silenceUntil,
		PausedHashCount: paused,
	}
	m.snapshot = snap
	return m.state, nil
}

// Deactivate resumes the previously-paused torrents (best effort) and
// clears state.
func (m *Manager) Deactivate(ctx context.Context) (State, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	if !m.state.Active {
		return m.state, errors.New("emergency: not active")
	}
	if m.clients != nil {
		for id, hashes := range m.snapshot.PausedByClient {
			c, ok := m.clients.Get(id)
			if !ok {
				continue
			}
			if len(hashes) > 0 {
				_ = c.Resume(ctx, hashes)
			}
		}
	}
	_, _ = m.db.ExecContext(ctx, `
		INSERT INTO audit_log(timestamp, actor, action, target_type, target_id)
		VALUES (?, 'operator', 'emergency_deactivate', 'emergency', 'state')
	`, time.Now().Unix())
	m.state = State{}
	m.snapshot = snapshotPayload{}
	return m.state, nil
}

// Current returns the current state.
func (m *Manager) Current() State {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.state
}

// IsSilenced returns true if notifications should be suppressed.
func (m *Manager) IsSilenced() bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	if !m.state.Active {
		return false
	}
	return time.Now().Unix() < m.state.SilenceUntil
}
