package store

import (
	"database/sql"
	"encoding/json"
	"time"
)

// On-demand app log tails (Part D) — the pull-based counterpart of the controller's
// log_tails report section. The hub stores an operator intent in log_tail_requests,
// advertises it on the report ACK (log_tail_requests: [app…]), and when the tail lands
// on the NEXT report it is stored here and the request is cleared (consume-once).
// Tails are transient custody: the last 2 per (customer, app) are kept, older pruned.

// AppLogTail is one received, ordered log tail.
type AppLogTail struct {
	ID          int
	CustomerID  string
	AppName     string
	CollectedAt time.Time
	ReceivedAt  time.Time
	Lines       []string
}

// RequestLogTail records (or refreshes) the operator's pending request for one app's
// log tail. One active request per (customer, app) — a re-click refreshes requested_at.
func (s *Store) RequestLogTail(customerID, appName string) error {
	_, err := s.db.Exec(`
		INSERT INTO log_tail_requests (customer_id, app_name, requested_at)
		VALUES (?, ?, ?)
		ON CONFLICT(customer_id, app_name) DO UPDATE SET requested_at = excluded.requested_at`,
		customerID, appName, time.Now().UTC())
	return err
}

// GetPendingLogTailRequests returns the app names with a pending tail request for the
// customer — the value advertised on the report ACK. nil/empty = nothing pending.
func (s *Store) GetPendingLogTailRequests(customerID string) ([]string, error) {
	rows, err := s.db.Query(`SELECT app_name FROM log_tail_requests WHERE customer_id = ? ORDER BY app_name`, customerID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var apps []string
	for rows.Next() {
		var app string
		if err := rows.Scan(&app); err == nil {
			apps = append(apps, app)
		}
	}
	return apps, rows.Err()
}

// SaveAppLogTail stores a received tail, prunes to the newest 2 per (customer, app),
// and clears the pending request (consume-once — without the clear the ACK would keep
// advertising the request and the controller would ship the tail every cycle).
func (s *Store) SaveAppLogTail(customerID, appName string, collectedAt time.Time, lines []string) error {
	linesJSON, err := json.Marshal(lines)
	if err != nil {
		return err
	}
	tx, err := s.db.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	if _, err := tx.Exec(`
		INSERT INTO app_log_tails (customer_id, app_name, collected_at, received_at, lines_json)
		VALUES (?, ?, ?, ?, ?)`,
		customerID, appName, collectedAt, time.Now().UTC(), string(linesJSON)); err != nil {
		return err
	}
	// Keep the newest 2 per (customer, app).
	if _, err := tx.Exec(`
		DELETE FROM app_log_tails
		WHERE customer_id = ? AND app_name = ? AND id NOT IN (
			SELECT id FROM app_log_tails WHERE customer_id = ? AND app_name = ?
			ORDER BY id DESC LIMIT 2
		)`, customerID, appName, customerID, appName); err != nil {
		return err
	}
	// Consume-once: the fulfilled request is cleared.
	if _, err := tx.Exec(`DELETE FROM log_tail_requests WHERE customer_id = ? AND app_name = ?`,
		customerID, appName); err != nil {
		return err
	}
	return tx.Commit()
}

// GetCustomerLogTails returns all stored tails for a customer, newest first.
func (s *Store) GetCustomerLogTails(customerID string) ([]AppLogTail, error) {
	rows, err := s.db.Query(`
		SELECT id, customer_id, app_name, collected_at, received_at, lines_json
		FROM app_log_tails WHERE customer_id = ?
		ORDER BY id DESC`, customerID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var tails []AppLogTail
	for rows.Next() {
		t, err := scanLogTail(rows)
		if err != nil {
			continue
		}
		tails = append(tails, t)
	}
	return tails, rows.Err()
}

// GetLogTail returns one stored tail by id, scoped to the customer (no cross-customer reads).
func (s *Store) GetLogTail(id int, customerID string) (*AppLogTail, error) {
	rows, err := s.db.Query(`
		SELECT id, customer_id, app_name, collected_at, received_at, lines_json
		FROM app_log_tails WHERE id = ? AND customer_id = ?`, id, customerID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	if !rows.Next() {
		return nil, rows.Err()
	}
	t, err := scanLogTail(rows)
	if err != nil {
		return nil, err
	}
	return &t, nil
}

func scanLogTail(rows *sql.Rows) (AppLogTail, error) {
	var t AppLogTail
	var linesJSON string
	if err := rows.Scan(&t.ID, &t.CustomerID, &t.AppName, &t.CollectedAt, &t.ReceivedAt, &linesJSON); err != nil {
		return t, err
	}
	json.Unmarshal([]byte(linesJSON), &t.Lines)
	return t, nil
}
