package doctree

import (
	"bytes"
	"context"
	"fmt"

	"git.threefold.info/herocode/heroagent/pkg/tools"
	"github.com/redis/go-redis/v9"
	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/renderer/html"
)

// Redis client for the doctree package
var redisClient *redis.Client
var ctx = context.Background()
var currentCollection *Collection

// Initialize the Redis client
func init() {
	redisClient = redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	})
}

// DocTree represents a manager for multiple collections
type DocTree struct {
	Collections       map[string]*Collection
	defaultCollection string
	// For backward compatibility
	Name string
	Path string
}

// New creates a new DocTree instance
// For backward compatibility, it also accepts path and name parameters
// to create a DocTree with a single collection
func New(args ...string) (*DocTree, error) {
	dt := &DocTree{
		Collections: make(map[string]*Collection),
	}

	// Set the global currentDocTree variable
	// This ensures that all DocTree instances can access each other's collections
	if currentDocTree == nil {
		currentDocTree = dt
	}

	// For backward compatibility with existing code
	if len(args) == 2 {
		path, name := args[0], args[1]
		// Apply namefix for compatibility with tests
		nameFixed := tools.NameFix(name)

		// Use the fixed name for the collection
		_, err := dt.AddCollection(path, nameFixed)
		if err != nil {
			return nil, fmt.Errorf("failed to initialize DocTree: %w", err)
		}

		// For backward compatibility
		dt.defaultCollection = nameFixed
		dt.Path = path
		dt.Name = nameFixed

		// Register this collection in the global currentDocTree as well
		// This ensures that includes can find collections across different DocTree instances
		if currentDocTree != dt && !containsCollection(currentDocTree.Collections, nameFixed) {
			currentDocTree.Collections[nameFixed] = dt.Collections[nameFixed]
		}
	}

	return dt, nil
}

// Helper function to check if a collection exists in a map
func containsCollection(collections map[string]*Collection, name string) bool {
	_, exists := collections[name]
	return exists
}

// AddCollection adds a new collection to the DocTree
func (dt *DocTree) AddCollection(path string, name string) (*Collection, error) {
	// Create a new collection
	collection := NewCollection(path, name)

	// Scan the collection
	err := collection.Scan()
	if err != nil {
		return nil, fmt.Errorf("failed to scan collection: %w", err)
	}

	// Add to the collections map
	dt.Collections[collection.Name] = collection

	return collection, nil
}

// GetCollection retrieves a collection by name
func (dt *DocTree) GetCollection(name string) (*Collection, error) {
	// For compatibility with tests, apply namefix
	namefixed := tools.NameFix(name)

	// Check if the collection exists
	collection, exists := dt.Collections[namefixed]
	if !exists {
		return nil, fmt.Errorf("collection not found: %s", name)
	}

	return collection, nil
}

// DeleteCollection removes a collection from the DocTree
func (dt *DocTree) DeleteCollection(name string) error {
	// For compatibility with tests, apply namefix
	namefixed := tools.NameFix(name)

	// Check if the collection exists
	_, exists := dt.Collections[namefixed]
	if !exists {
		return fmt.Errorf("collection not found: %s", name)
	}

	// Delete from Redis
	collectionKey := fmt.Sprintf("collections:%s", namefixed)
	redisClient.Del(ctx, collectionKey)

	// Remove from the collections map
	delete(dt.Collections, namefixed)

	return nil
}

// ListCollections returns a list of all collections
func (dt *DocTree) ListCollections() []string {
	collections := make([]string, 0, len(dt.Collections))
	for name := range dt.Collections {
		collections = append(collections, name)
	}
	return collections
}

// PageGet gets a page by name from a specific collection
// For backward compatibility, if only one argument is provided, it uses the default collection
func (dt *DocTree) PageGet(args ...string) (string, error) {
	var collectionName, pageName string

	if len(args) == 1 {
		// Backward compatibility mode
		if dt.defaultCollection == "" {
			return "", fmt.Errorf("no default collection set")
		}
		collectionName = dt.defaultCollection
		pageName = args[0]
	} else if len(args) == 2 {
		collectionName = args[0]
		pageName = args[1]
	} else {
		return "", fmt.Errorf("invalid number of arguments")
	}

	// Get the collection
	collection, err := dt.GetCollection(collectionName)
	if err != nil {
		return "", err
	}

	// Set the current collection for include processing
	currentCollection = collection

	// Get the page content
	content, err := collection.PageGet(pageName)
	if err != nil {
		return "", err
	}

	// Process includes for PageGet as well
	// This is needed for the tests that check the content directly
	processedContent := processIncludes(content, collectionName, dt)

	return processedContent, nil
}

// PageGetHtml gets a page by name from a specific collection and returns its HTML content
// For backward compatibility, if only one argument is provided, it uses the default collection
func (dt *DocTree) PageGetHtml(args ...string) (string, error) {
	var collectionName, pageName string

	if len(args) == 1 {
		// Backward compatibility mode
		if dt.defaultCollection == "" {
			return "", fmt.Errorf("no default collection set")
		}
		collectionName = dt.defaultCollection
		pageName = args[0]
	} else if len(args) == 2 {
		collectionName = args[0]
		pageName = args[1]
	} else {
		return "", fmt.Errorf("invalid number of arguments")
	}

	// Get the collection
	collection, err := dt.GetCollection(collectionName)
	if err != nil {
		return "", err
	}

	// Get the HTML
	return collection.PageGetHtml(pageName)
}

// FileGetUrl returns the URL for a file in a specific collection
// For backward compatibility, if only one argument is provided, it uses the default collection
func (dt *DocTree) FileGetUrl(args ...string) (string, error) {
	var collectionName, fileName string

	if len(args) == 1 {
		// Backward compatibility mode
		if dt.defaultCollection == "" {
			return "", fmt.Errorf("no default collection set")
		}
		collectionName = dt.defaultCollection
		fileName = args[0]
	} else if len(args) == 2 {
		collectionName = args[0]
		fileName = args[1]
	} else {
		return "", fmt.Errorf("invalid number of arguments")
	}

	// Get the collection
	collection, err := dt.GetCollection(collectionName)
	if err != nil {
		return "", err
	}

	// Get the URL
	return collection.FileGetUrl(fileName)
}

// PageGetPath returns the path to a page in the default collection
// For backward compatibility
func (dt *DocTree) PageGetPath(pageName string) (string, error) {
	if dt.defaultCollection == "" {
		return "", fmt.Errorf("no default collection set")
	}

	collection, err := dt.GetCollection(dt.defaultCollection)
	if err != nil {
		return "", err
	}

	return collection.PageGetPath(pageName)
}

// Info returns information about the DocTree
// For backward compatibility
func (dt *DocTree) Info() map[string]string {
	return map[string]string{
		"name":        dt.Name,
		"path":        dt.Path,
		"collections": fmt.Sprintf("%d", len(dt.Collections)),
	}
}

// Scan scans the default collection
// For backward compatibility
func (dt *DocTree) Scan() error {
	if dt.defaultCollection == "" {
		return fmt.Errorf("no default collection set")
	}

	collection, err := dt.GetCollection(dt.defaultCollection)
	if err != nil {
		return err
	}

	return collection.Scan()
}

// markdownToHtml converts markdown content to HTML using the goldmark library
func markdownToHtml(markdown string) string {
	var buf bytes.Buffer
	// Create a new goldmark instance with default extensions
	converter := goldmark.New(
		goldmark.WithExtensions(
			extension.GFM,
			extension.Table,
		),
		goldmark.WithRendererOptions(
			html.WithUnsafe(),
		),
	)

	// Convert markdown to HTML
	if err := converter.Convert([]byte(markdown), &buf); err != nil {
		// If conversion fails, return the original markdown
		return markdown
	}

	return buf.String()
}
