package browser

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

// CdpTab representa una pestaña/target devuelta por el endpoint /json de CDP.
// Campos publicos para que apps consumidoras puedan filtrar/render.
type CdpTab struct {
	ID                   string `json:"id"`
	Type                 string `json:"type"`                 // "page", "iframe", "service_worker", ...
	Title                string `json:"title"`
	URL                  string `json:"url"`
	WebSocketDebuggerURL string `json:"webSocketDebuggerUrl"`
	DevtoolsFrontendURL  string `json:"devtoolsFrontendUrl,omitempty"`
}

// CdpListTabs llama GET http://{host}:{port}/json y retorna la lista de
// targets. Sin filtrar por tipo — el caller decide si se queda solo con
// type=="page", incluye iframes, etc.
//
// host vacio = "localhost". No requiere websocket (CDP expone /json en HTTP).
func CdpListTabs(host string, port int) ([]CdpTab, error) {
	if host == "" {
		host = "localhost"
	}
	resp, err := http.Get(fmt.Sprintf("http://%s:%d/json", host, port))
	if err != nil {
		return nil, fmt.Errorf("cdp list tabs: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("cdp list tabs: status %d", resp.StatusCode)
	}
	var tabs []CdpTab
	if err := json.NewDecoder(resp.Body).Decode(&tabs); err != nil {
		return nil, fmt.Errorf("cdp list tabs: decode: %w", err)
	}
	return tabs, nil
}

// CdpNewTab abre una pestaña nueva via PUT /json/new?<startURL>. Si startURL
// es vacio Chrome abre about:blank. Retorna el CdpTab recien creado.
//
// Nota: desde Chrome 126 /json/new requiere PUT (no GET). Mantenemos el
// fallback a GET por compatibilidad con builds antiguos.
func CdpNewTab(host string, port int, startURL string) (CdpTab, error) {
	if host == "" {
		host = "localhost"
	}
	endpoint := fmt.Sprintf("http://%s:%d/json/new", host, port)
	if startURL != "" {
		endpoint += "?" + url.QueryEscape(startURL)
	}

	tryRequest := func(method string) (CdpTab, error) {
		var out CdpTab
		req, err := http.NewRequest(method, endpoint, nil)
		if err != nil {
			return out, err
		}
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			return out, err
		}
		defer resp.Body.Close()
		if resp.StatusCode != http.StatusOK {
			return out, fmt.Errorf("status %d", resp.StatusCode)
		}
		if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
			return out, fmt.Errorf("decode: %w", err)
		}
		return out, nil
	}

	tab, err := tryRequest(http.MethodPut)
	if err == nil && tab.ID != "" {
		return tab, nil
	}
	// Fallback GET (Chrome < 126).
	tab, err2 := tryRequest(http.MethodGet)
	if err2 == nil && tab.ID != "" {
		return tab, nil
	}
	if err == nil {
		err = err2
	}
	return CdpTab{}, fmt.Errorf("cdp new tab: %w", err)
}

// CdpCloseTab cierra una pestaña por su ID via GET /json/close/<id>.
// Util complemento — incluido aqui porque comparte estructura HTTP /json.
func CdpCloseTab(host string, port int, tabID string) error {
	if host == "" {
		host = "localhost"
	}
	if tabID == "" {
		return fmt.Errorf("cdp close tab: tabID vacio")
	}
	resp, err := http.Get(fmt.Sprintf("http://%s:%d/json/close/%s", host, port, url.PathEscape(tabID)))
	if err != nil {
		return fmt.Errorf("cdp close tab: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("cdp close tab: status %d", resp.StatusCode)
	}
	return nil
}

// CdpActivateTab pone la pestaña en foreground (focus) via /json/activate/<id>.
func CdpActivateTab(host string, port int, tabID string) error {
	if host == "" {
		host = "localhost"
	}
	if tabID == "" {
		return fmt.Errorf("cdp activate tab: tabID vacio")
	}
	resp, err := http.Get(fmt.Sprintf("http://%s:%d/json/activate/%s", host, port, url.PathEscape(tabID)))
	if err != nil {
		return fmt.Errorf("cdp activate tab: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("cdp activate tab: status %d", resp.StatusCode)
	}
	return nil
}
