package infra

import (
	"database/sql"
	"fmt"
	"net"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

// ServiceStatus holds the runtime status of a registered service app.
type ServiceStatus struct {
	AppID         string // e.g. "registry_api_go_infra"
	Name          string // e.g. "registry_api"
	UnitName      string // e.g. "registry_api.service"
	UnitActive    string // "active", "inactive", "failed", "not-installed", "unknown"
	Port          int    // declared port parsed from notes/description, 0 if none
	PortListening bool   // true if Port > 0 and 127.0.0.1:Port is accepting TCP connections
	HostMatch     string // pc_id from ~/.fn_pc, or "" if unreadable
}

var portRe = regexp.MustCompile(`\b([1-9][0-9]{3,4})\b`)

// ServicesStatus queries registry.db for apps tagged "service" and returns
// their current systemd unit state and port reachability.
func ServicesStatus(registryRoot string) ([]ServiceStatus, error) {
	dbPath := registryRoot + "/registry.db"
	db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&mode=ro")
	if err != nil {
		return nil, fmt.Errorf("services_status: open db: %w", err)
	}
	defer db.Close()

	rows, err := db.Query(`SELECT id, name, COALESCE(notes,''), COALESCE(description,'') FROM apps WHERE tags LIKE '%service%'`)
	if err != nil {
		return nil, fmt.Errorf("services_status: query: %w", err)
	}
	defer rows.Close()

	pcID, _ := readFnPC()

	var results []ServiceStatus
	for rows.Next() {
		var id, name, notes, description string
		if err := rows.Scan(&id, &name, &notes, &description); err != nil {
			continue
		}

		unit := name + ".service"
		active := queryUnitActive(unit)
		port := parseFirstPort(notes + " " + description)
		listening := false
		if port > 0 {
			listening = tcpListening("127.0.0.1", port, 500*time.Millisecond)
		}

		results = append(results, ServiceStatus{
			AppID:         id,
			Name:          name,
			UnitName:      unit,
			UnitActive:    active,
			Port:          port,
			PortListening: listening,
			HostMatch:     pcID,
		})
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("services_status: rows: %w", err)
	}

	return results, nil
}

// queryUnitActive runs systemctl is-active, trying --user first then system.
func queryUnitActive(unit string) string {
	// try user scope
	out, err := exec.Command("systemctl", "--user", "is-active", unit).Output()
	if err == nil {
		return strings.TrimSpace(string(out))
	}
	combined, _ := exec.Command("systemctl", "--user", "is-active", unit).CombinedOutput()
	if strings.Contains(string(combined), "could not be found") ||
		strings.Contains(string(combined), "not found") ||
		strings.Contains(string(combined), "No such") {
		// try system scope
		out2, err2 := exec.Command("systemctl", "is-active", unit).Output()
		if err2 == nil {
			return strings.TrimSpace(string(out2))
		}
		combined2, _ := exec.Command("systemctl", "is-active", unit).CombinedOutput()
		if strings.Contains(string(combined2), "could not be found") ||
			strings.Contains(string(combined2), "not found") ||
			strings.Contains(string(combined2), "No such") {
			return "not-installed"
		}
		s := strings.TrimSpace(string(out2))
		if s == "" {
			return "unknown"
		}
		return s
	}
	// systemctl returned non-zero but unit exists (e.g. "inactive", "failed")
	if len(out) > 0 {
		return strings.TrimSpace(string(out))
	}
	return "unknown"
}

// parseFirstPort returns the first integer in [1024, 65535] found in text.
func parseFirstPort(text string) int {
	for _, m := range portRe.FindAllString(text, -1) {
		n, err := strconv.Atoi(m)
		if err == nil && n >= 1024 && n <= 65535 {
			return n
		}
	}
	return 0
}

// tcpListening attempts a TCP connection to addr:port with the given timeout.
func tcpListening(host string, port int, timeout time.Duration) bool {
	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), timeout)
	if err != nil {
		return false
	}
	conn.Close()
	return true
}

