package infra

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"strings"
	"time"

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

// UnusedFunction represents a registry function with no known consumers.
type UnusedFunction struct {
	ID      string
	Name    string
	Lang    string
	Domain  string
	Tags    string // JSON array string, useful for detecting "deprecated" tags
	AgeDays int    // days since updated_at
}

// FindUnusedFunctions opens <registryRoot>/registry.db and returns all
// functions that are not referenced by any other function, app, or analysis
// via their uses_functions field.
//
// Pipelines with the "launcher" tag are included if nobody calls them —
// they are endpoint-only but still candidates if unlaunched and uncalled.
// Plain pipelines (kind = "pipeline", no "launcher" tag) are also included.
// Functions with kind = "pipeline" that have the "launcher" tag are excluded
// because they are intentionally terminal consumers.
func FindUnusedFunctions(registryRoot string) ([]UnusedFunction, error) {
	dbPath := registryRoot + "/registry.db"
	db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL")
	if err != nil {
		return nil, fmt.Errorf("find_unused_functions: open db: %w", err)
	}
	defer db.Close()

	// Build the set of used IDs from uses_functions across functions, apps, and analyses.
	usedIDs := make(map[string]struct{})

	type usesRow struct {
		usesJSON string
	}

	collectUsed := func(query string) error {
		rows, err := db.Query(query)
		if err != nil {
			return err
		}
		defer rows.Close()
		for rows.Next() {
			var raw string
			if err := rows.Scan(&raw); err != nil {
				return err
			}
			var ids []string
			if err := json.Unmarshal([]byte(raw), &ids); err != nil {
				continue // malformed JSON, skip
			}
			for _, id := range ids {
				id = strings.TrimSpace(id)
				if id != "" {
					usedIDs[id] = struct{}{}
				}
			}
		}
		return rows.Err()
	}

	queries := []string{
		"SELECT uses_functions FROM functions WHERE uses_functions != '[]'",
		"SELECT uses_functions FROM apps WHERE uses_functions != '[]'",
		"SELECT uses_functions FROM analysis WHERE uses_functions != '[]'",
	}
	for _, q := range queries {
		if err := collectUsed(q); err != nil {
			return nil, fmt.Errorf("find_unused_functions: collecting used IDs: %w", err)
		}
	}

	// Query all functions; filter out pipelines with "launcher" tag (intentional terminals).
	rows, err := db.Query(`
		SELECT id, name, lang, domain, tags, updated_at, kind
		FROM functions
		ORDER BY updated_at ASC
	`)
	if err != nil {
		return nil, fmt.Errorf("find_unused_functions: query functions: %w", err)
	}
	defer rows.Close()

	now := time.Now().UTC()
	var result []UnusedFunction

	for rows.Next() {
		var (
			id, name, lang, domain, tags, updatedAt, kind string
		)
		if err := rows.Scan(&id, &name, &lang, &domain, &tags, &updatedAt, &kind); err != nil {
			return nil, fmt.Errorf("find_unused_functions: scan: %w", err)
		}

		// Skip if this function is used by someone.
		if _, used := usedIDs[id]; used {
			continue
		}

		// Pipelines with "launcher" tag are intentional consumers — skip them.
		if kind == "pipeline" {
			var tagList []string
			_ = json.Unmarshal([]byte(tags), &tagList)
			for _, t := range tagList {
				if strings.TrimSpace(t) == "launcher" {
					goto next
				}
			}
		}

		{
			updatedTime, err := time.Parse(time.RFC3339, updatedAt)
			if err != nil {
				// Try without timezone suffix
				updatedTime, err = time.Parse("2006-01-02T15:04:05Z", updatedAt)
				if err != nil {
					updatedTime = now
				}
			}
			ageDays := int(now.Sub(updatedTime).Hours() / 24)
			result = append(result, UnusedFunction{
				ID:      id,
				Name:    name,
				Lang:    lang,
				Domain:  domain,
				Tags:    tags,
				AgeDays: ageDays,
			})
		}

	next:
	}

	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("find_unused_functions: rows: %w", err)
	}

	return result, nil
}
