package store

import (
	"context"
	"crypto/subtle"
	"database/sql"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"time"

	"mengyamonitor-backend-server/internal/model"

	"github.com/google/uuid"
	_ "modernc.org/sqlite"
)

type Store struct {
	db *sql.DB
}

func Open(dbPath string) (*Store, error) {
	dir := filepath.Dir(dbPath)
	if dir != "." && dir != "" {
		if err := os.MkdirAll(dir, 0o755); err != nil {
			return nil, err
		}
	}
	db, err := sql.Open("sqlite", dbPath+"?_pragma=foreign_keys(1)")
	if err != nil {
		return nil, err
	}
	if err := db.Ping(); err != nil {
		_ = db.Close()
		return nil, err
	}
	s := &Store{db: db}
	if err := s.migrate(); err != nil {
		_ = db.Close()
		return nil, err
	}
	return s, nil
}

func (s *Store) Close() error {
	return s.db.Close()
}

func (s *Store) migrate() error {
	const ddl = `
CREATE TABLE IF NOT EXISTS servers (
	id TEXT PRIMARY KEY,
	name TEXT NOT NULL,
	url TEXT NOT NULL,
	enabled INTEGER NOT NULL DEFAULT 1,
	sort_order INTEGER NOT NULL DEFAULT 0,
	created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_servers_sort ON servers(sort_order);
`
	if _, err := s.db.Exec(ddl); err != nil {
		return err
	}
	return s.ensureAgentKeyColumn()
}

func (s *Store) ensureAgentKeyColumn() error {
	rows, err := s.db.Query(`PRAGMA table_info(servers)`)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		var cid int
		var name, typ string
		var notnull, pk int
		var dflt sql.NullString
		if err := rows.Scan(&cid, &name, &typ, &notnull, &dflt, &pk); err != nil {
			return err
		}
		if name == "agent_key" {
			return nil
		}
	}
	if err := rows.Err(); err != nil {
		return err
	}
	if _, err = s.db.Exec(`ALTER TABLE servers ADD COLUMN agent_key TEXT`); err != nil {
		return err
	}
	// Backfill keys for existing rows so gRPC can be enabled without manual SQL.
	bRows, err := s.db.Query(`SELECT id FROM servers WHERE agent_key IS NULL OR agent_key = ''`)
	if err != nil {
		return err
	}
	var ids []string
	for bRows.Next() {
		var id string
		if err := bRows.Scan(&id); err != nil {
			_ = bRows.Close()
			return err
		}
		ids = append(ids, id)
	}
	_ = bRows.Close()
	for _, id := range ids {
		if _, err := s.db.Exec(`UPDATE servers SET agent_key = ? WHERE id = ?`, uuid.NewString(), id); err != nil {
			return err
		}
	}
	return nil
}

func (s *Store) ListEnabled(ctx context.Context) ([]model.MonitoredServer, error) {
	rows, err := s.db.QueryContext(ctx, `
SELECT id, name, url, enabled, sort_order, agent_key, created_at
FROM servers WHERE enabled = 1 ORDER BY sort_order ASC, created_at ASC`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanServers(rows)
}

func (s *Store) ListAll(ctx context.Context) ([]model.MonitoredServer, error) {
	rows, err := s.db.QueryContext(ctx, `
SELECT id, name, url, enabled, sort_order, agent_key, created_at
FROM servers ORDER BY sort_order ASC, created_at ASC`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanServers(rows)
}

func scanServers(rows *sql.Rows) ([]model.MonitoredServer, error) {
	out := make([]model.MonitoredServer, 0)
	for rows.Next() {
		var m model.MonitoredServer
		var enabled int
		var created, agKey sql.NullString
		if err := rows.Scan(&m.ID, &m.Name, &m.URL, &enabled, &m.SortOrder, &agKey, &created); err != nil {
			return nil, err
		}
		m.Enabled = enabled != 0
		if agKey.Valid {
			m.AgentKey = agKey.String
		}
		if t, err := time.Parse(time.RFC3339Nano, created.String); err == nil {
			m.CreatedAt = t
		}
		out = append(out, m)
	}
	return out, rows.Err()
}

func (s *Store) Create(ctx context.Context, in model.CreateServerInput) (*model.MonitoredServer, error) {
	name := strings.TrimSpace(in.Name)
	urlStr := normalizeURL(strings.TrimSpace(in.URL))
	if name == "" {
		return nil, errors.New("invalid name")
	}
	enabled := true
	if in.Enabled != nil {
		enabled = *in.Enabled
	}
	var maxOrder int
	_ = s.db.QueryRowContext(ctx, `SELECT COALESCE(MAX(sort_order), -1) FROM servers`).Scan(&maxOrder)
	id := uuid.NewString()
	agentKey := uuid.NewString()
	now := time.Now().UTC()
	_, err := s.db.ExecContext(ctx, `
INSERT INTO servers (id, name, url, enabled, sort_order, agent_key, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
		id, name, urlStr, boolToInt(enabled), maxOrder+1, agentKey, now.Format(time.RFC3339Nano))
	if err != nil {
		return nil, err
	}
	return &model.MonitoredServer{
		ID: id, Name: name, URL: urlStr, Enabled: enabled, SortOrder: maxOrder + 1, AgentKey: agentKey, CreatedAt: now,
	}, nil
}

func (s *Store) Update(ctx context.Context, id string, in model.UpdateServerInput) (*model.MonitoredServer, error) {
	cur, err := s.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	name := cur.Name
	urlStr := cur.URL
	enabled := cur.Enabled
	if in.Name != "" {
		name = strings.TrimSpace(in.Name)
	}
	if in.URL != nil {
		urlStr = normalizeURL(strings.TrimSpace(*in.URL))
	}
	if in.Enabled != nil {
		enabled = *in.Enabled
	}
	if name == "" {
		return nil, errors.New("invalid name")
	}
	agentKey := cur.AgentKey
	if in.AgentKey != nil {
		agentKey = *in.AgentKey
	}
	_, err = s.db.ExecContext(ctx, `UPDATE servers SET name = ?, url = ?, enabled = ?, agent_key = ? WHERE id = ?`,
		name, urlStr, boolToInt(enabled), nullIfEmpty(agentKey), id)
	if err != nil {
		return nil, err
	}
	cur.Name, cur.URL, cur.Enabled, cur.AgentKey = name, urlStr, enabled, agentKey
	return cur, nil
}

func (s *Store) Get(ctx context.Context, id string) (*model.MonitoredServer, error) {
	var m model.MonitoredServer
	var enabled int
	var created, agKey sql.NullString
	err := s.db.QueryRowContext(ctx, `
SELECT id, name, url, enabled, sort_order, agent_key, created_at FROM servers WHERE id = ?`, id).
		Scan(&m.ID, &m.Name, &m.URL, &enabled, &m.SortOrder, &agKey, &created)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, err
	}
	if err != nil {
		return nil, err
	}
	m.Enabled = enabled != 0
	if agKey.Valid {
		m.AgentKey = agKey.String
	}
	if t, err := time.Parse(time.RFC3339Nano, created.String); err == nil {
		m.CreatedAt = t
	}
	return &m, nil
}

// VerifyAgent checks server_id + agent_key for gRPC ingest.
func (s *Store) VerifyAgent(ctx context.Context, id, key string) (*model.MonitoredServer, error) {
	srv, err := s.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	if !srv.Enabled {
		return nil, errors.New("disabled")
	}
	if srv.AgentKey == "" {
		return nil, errors.New("agent_key not set")
	}
	if subtle.ConstantTimeCompare([]byte(srv.AgentKey), []byte(key)) != 1 {
		return nil, errors.New("invalid agent_key")
	}
	return srv, nil
}

func nullIfEmpty(s string) any {
	if s == "" {
		return nil
	}
	return s
}

func (s *Store) Delete(ctx context.Context, id string) error {
	res, err := s.db.ExecContext(ctx, `DELETE FROM servers WHERE id = ?`, id)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (s *Store) Reorder(ctx context.Context, ids []string) error {
	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer func() { _ = tx.Rollback() }()
	for i, id := range ids {
		if _, err := tx.ExecContext(ctx, `UPDATE servers SET sort_order = ? WHERE id = ?`, i, id); err != nil {
			return err
		}
	}
	return tx.Commit()
}

func boolToInt(b bool) int {
	if b {
		return 1
	}
	return 0
}

func normalizeURL(u string) string {
	if u == "" {
		return ""
	}
	if !strings.HasPrefix(strings.ToLower(u), "http://") && !strings.HasPrefix(strings.ToLower(u), "https://") {
		return "http://" + u
	}
	return u
}
