package browser

import (
	"context"
	"log"
	"math/rand"
	"os/exec"
	"time"

	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/cdproto/page"
	"github.com/chromedp/chromedp"
)

type ChromeInstance struct {
	WSURL string
	Cmd   *exec.Cmd
}

type Browser struct {
	Instance    *ChromeInstance
	allocCtx    context.Context
	allocCancel context.CancelFunc
	ctx         context.Context
	cancel      context.CancelFunc
}

// NewBrowser launches Chrome and creates a shared allocator.
// Use NewTab() to create separate tabs for concurrent use.
func NewBrowser(ctx context.Context) (*Browser, error) {
	wsURL, cmd, err := launchChrome()

	if err != nil {
		return nil, err
	}

	allocCtx, allocCancel := chromedp.NewRemoteAllocator(ctx, wsURL)

	// Create initial browser context (first tab)
	browserCtx, cancel := chromedp.NewContext(allocCtx)

	return &Browser{
		Instance: &ChromeInstance{
			WSURL: wsURL,
			Cmd:   cmd,
		},
		allocCtx:    allocCtx,
		allocCancel: allocCancel,
		ctx:         browserCtx,
		cancel:      cancel,
	}, nil
}

// NewTab creates a new browser tab that can be used concurrently.
// Returns a new Browser instance sharing the same Chrome process.
func (b *Browser) NewTab() (*Browser, error) {
	tabCtx, cancel := chromedp.NewContext(b.allocCtx)

	return &Browser{
		Instance:    b.Instance,
		allocCtx:    b.allocCtx,
		allocCancel: nil, // Don't close allocator when closing a tab
		ctx:         tabCtx,
		cancel:      cancel,
	}, nil
}

func (b *Browser) Close() error {
	if b.cancel != nil {
		b.cancel()
	}

	if b.allocCancel != nil {
		b.allocCancel()
	}

	// Only kill Chrome process if this is the main browser (has allocCancel),
	// not a tab created via NewTab()
	if b.allocCancel != nil && b.Instance != nil && b.Instance.Cmd != nil && b.Instance.Cmd.Process != nil {
		return b.Instance.Cmd.Process.Kill()
	}

	return nil
}

func (b *Browser) Navigate(url string) error {
	chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36")

	err := chromedp.Run(b.ctx,
		chromedp.Navigate(url),
	)

	if err != nil {
		return err
	}

	return nil
}

func (b *Browser) WaitVisible(selector string) error {
	ctx, cancel := context.WithTimeout(b.ctx, 30*time.Second)
	defer cancel()

	err := chromedp.Run(ctx,
		chromedp.WaitVisible(selector, chromedp.ByQuery),
	)

	if err != nil {
		return err
	}

	return nil
}

func (b *Browser) GetNodes(selector string) ([]*cdp.Node, error) {
	var nodes []*cdp.Node

	err := chromedp.Run(b.ctx,
		chromedp.Nodes("[data-snc]", &nodes, chromedp.ByQueryAll),
	)

	if err != nil {
		return nodes, err
	}

	return nodes, nil
}

func (b *Browser) GetTextFromNode(node *cdp.Node, selector string) (string, error) {
	var text string

	err := chromedp.Run(b.ctx,
		chromedp.Text(selector, &text, chromedp.ByQuery, chromedp.FromNode(node)),
	)

	if err != nil {
		return "", err
	}

	return text, nil
}

func (b *Browser) GetAttributeFromNode(node *cdp.Node, selector string, attr string) (string, error) {
	var value string

	err := chromedp.Run(b.ctx,
		chromedp.AttributeValue(selector, attr, &value, nil, chromedp.ByQuery, chromedp.FromNode(node)),
	)

	if err != nil {
		return "", err
	}

	return value, nil
}

func Evaluate[T any](b *Browser, expression string) (T, error) {
	var result T

	err := chromedp.Run(b.ctx,
		chromedp.Evaluate(expression, &result),
	)

	if err != nil {
		return result, err
	}

	return result, nil
}

func (b *Browser) GetCurrentLocation() (string, error) {
	var finalURL string
	err := chromedp.Run(b.ctx,
		chromedp.Location(&finalURL),
	)

	if err != nil {
		log.Printf("Failed to get current location: %v", err)
		return "", err
	}

	return finalURL, nil
}

func (b *Browser) GetHTML() (string, error) {
	var htmlContent string

	err := chromedp.Run(b.ctx,
		chromedp.OuterHTML("html", &htmlContent, chromedp.ByQuery),
	)
	if err != nil {
		log.Printf("Failed to get HTML content: %v", err)
		return "", err
	}

	return htmlContent, nil
}

func (b *Browser) WaitForNetworkIdle(timeout time.Duration) error {
	err := chromedp.Run(b.ctx,
		chromedp.ActionFunc(func(ctx context.Context) error {
			return page.SetLifecycleEventsEnabled(true).Do(ctx)
		}),
	)
	if err != nil {
		return err
	}

	ch := make(chan struct{}, 1)
	chromedp.ListenTarget(b.ctx, func(ev interface{}) {
		if e, ok := ev.(*page.EventLifecycleEvent); ok {
			if e.Name == "networkIdle" {
				select {
				case ch <- struct{}{}:
				default:
				}
			}
		}
	})

	select {
	case <-ch:
		return nil
	case <-time.After(timeout):
		// Timeout reached, but don't error - page may still be usable
		return nil
	case <-b.ctx.Done():
		return b.ctx.Err()
	}
}

// RandomDelay waits for a random duration between min and max milliseconds
func RandomDelay(minMs, maxMs int) {
	delay := time.Duration(minMs+rand.Intn(maxMs-minMs)) * time.Millisecond
	time.Sleep(delay)
}

// Sleep waits for the specified duration
func (b *Browser) Sleep(d time.Duration) {
	time.Sleep(d)
}
