package browser

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strings"
)

// cdpTarget representa un target CDP del endpoint /json.
type cdpTarget struct {
	ID                   string `json:"id"`
	Type                 string `json:"type"`
	Title                string `json:"title"`
	URL                  string `json:"url"`
	WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
}

// cdpGetPageWSURL obtiene el webSocketDebuggerUrl de la primera tab de tipo "page"
// via el endpoint /json. Si no hay ninguna, crea una nueva con /json/new.
func cdpGetPageWSURL(host string, port int) (string, error) {
	if host == "" {
		host = "localhost"
	}
	resp, err := http.Get(fmt.Sprintf("http://%s:%d/json", host, port))
	if err != nil {
		return "", fmt.Errorf("cdp targets: %w", err)
	}
	defer resp.Body.Close()

	var targets []cdpTarget
	if err := json.NewDecoder(resp.Body).Decode(&targets); err != nil {
		return "", fmt.Errorf("cdp targets: decode: %w", err)
	}

	// Buscar la primera tab de tipo "page"
	for _, t := range targets {
		if t.Type == "page" && t.WebSocketDebuggerURL != "" {
			return t.WebSocketDebuggerURL, nil
		}
	}

	// No hay tabs — crear una nueva via /json/new
	newResp, err := http.Get(fmt.Sprintf("http://%s:%d/json/new", host, port))
	if err != nil {
		return "", fmt.Errorf("cdp new tab: %w", err)
	}
	defer newResp.Body.Close()

	var newTarget cdpTarget
	if err := json.NewDecoder(newResp.Body).Decode(&newTarget); err != nil {
		return "", fmt.Errorf("cdp new tab: decode: %w", err)
	}
	if newTarget.WebSocketDebuggerURL == "" {
		return "", fmt.Errorf("cdp new tab: webSocketDebuggerUrl vacio")
	}
	return newTarget.WebSocketDebuggerURL, nil
}

// CdpConnect se conecta al endpoint CDP en localhost:{port} y retorna una CDPConn lista para usar.
// Conecta a la primera tab de tipo "page" (que soporta Page.*, Runtime.*, Input.*).
// Si no hay tabs disponibles, crea una nueva via /json/new.
// Realiza el handshake WebSocket RFC 6455 sobre TCP puro (sin dependencias externas).
func CdpConnect(port int) (*CDPConn, error) {
	return CdpConnectHost("localhost", port)
}

// CdpConnectHost es como CdpConnect pero permite especificar el host.
// Util para WSL2 donde Chrome Windows escucha en una IP distinta a localhost.
func CdpConnectHost(host string, port int) (*CDPConn, error) {
	if host == "" {
		host = "localhost"
	}
	wsURL, err := cdpGetPageWSURL(host, port)
	if err != nil {
		return nil, fmt.Errorf("cdp connect: %w", err)
	}

	// Parsear la URL del WebSocket para extraer host y path
	u, err := url.Parse(wsURL)
	if err != nil {
		return nil, fmt.Errorf("cdp connect: parsear ws url %q: %w", wsURL, err)
	}

	wsHost := u.Host
	if !strings.Contains(wsHost, ":") {
		wsHost = wsHost + ":80"
	}

	// Abrir conexion TCP
	tcpConn, err := net.Dial("tcp", wsHost)
	if err != nil {
		return nil, fmt.Errorf("cdp connect: tcp dial %s: %w", wsHost, err)
	}

	// Realizar handshake WebSocket
	path := u.RequestURI()
	reader, err := wsHandshake(tcpConn, wsHost, path)
	if err != nil {
		tcpConn.Close()
		return nil, fmt.Errorf("cdp connect: ws handshake: %w", err)
	}

	c := &CDPConn{
		conn:    tcpConn,
		reader:  reader,
		port:    port,
		pending: make(map[int64]chan cdpResponse),
	}

	// Iniciar goroutine de lectura
	go c.readLoop()

	return c, nil
}
