// blocked services fetches the most recent Hostlists Registry blocked service
// index and transforms the filters from it to AdGuard Home's data and code
// formats.
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"os"
	"slices"
	"strings"
	"text/template"
	"time"

	"github.com/AdguardTeam/golibs/log"
)

func main() {
	urlStr := "https://adguardteam.github.io/HostlistsRegistry/assets/services.json"
	if v, ok := os.LookupEnv("URL"); ok {
		urlStr = v
	}

	// Validate the URL.
	_, err := url.Parse(urlStr)
	check(err)

	c := &http.Client{
		Timeout: 10 * time.Second,
	}

	resp, err := c.Get(urlStr)
	check(err)
	defer log.OnCloserError(resp.Body, log.ERROR)

	if resp.StatusCode != http.StatusOK {
		panic(fmt.Errorf("expected code %d, got %d", http.StatusOK, resp.StatusCode))
	}

	hlSvcs := &hlServices{}
	err = json.NewDecoder(resp.Body).Decode(hlSvcs)
	check(err)

	// Sort all services and rules to make the output more predictable.
	slices.SortStableFunc(hlSvcs.BlockedServices, func(a, b *hlServicesService) (res int) {
		return strings.Compare(a.ID, b.ID)
	})
	for _, s := range hlSvcs.BlockedServices {
		slices.Sort(s.Rules)
	}

	// Use another set of delimiters to prevent them interfering with the Go
	// code.
	tmpl, err := template.New("main").Delims("<%", "%>").Funcs(template.FuncMap{
		"isnotlast": func(idx, sliceLen int) (ok bool) { return idx != sliceLen-1 },
	}).Parse(tmplStr)
	check(err)

	f, err := os.OpenFile(
		"./internal/filtering/servicelist.go",
		os.O_CREATE|os.O_TRUNC|os.O_WRONLY,
		0o644,
	)
	check(err)
	defer log.OnCloserError(f, log.ERROR)

	err = tmpl.Execute(f, hlSvcs)
	check(err)
}

// tmplStr is the template for the Go source file with the services.
const tmplStr = `// Code generated by go run ./scripts/blocked-services/main.go; DO NOT EDIT.

package filtering

// blockedService represents a single blocked service.
type blockedService struct {
	ID      string   ` + "`" + `json:"id"` + "`" + `
	Name    string   ` + "`" + `json:"name"` + "`" + `
	IconSVG []byte   ` + "`" + `json:"icon_svg"` + "`" + `
	Rules   []string ` + "`" + `json:"rules"` + "`" + `
}

// blockedServices contains raw blocked service data.
var blockedServices = []blockedService{<% $l := len .BlockedServices %>
	<%- range $i, $s := .BlockedServices %>{
	ID:      <% printf "%q" $s.ID %>,
	Name:    <% printf "%q" $s.Name %>,
	IconSVG: []byte(<% printf "%q" $s.IconSVG %>),
	Rules: []string{<% range $s.Rules %>
		<% printf "%q" . %>,<% end %>
	},
}<% if isnotlast $i $l %>, <% end %><% end %>}
`

// check is a simple error-checking helper for scripts.
func check(err error) {
	if err != nil {
		panic(err)
	}
}

// hlServices is the JSON structure for the Hostlists Registry blocked service
// index.
type hlServices struct {
	BlockedServices []*hlServicesService `json:"blocked_services"`
}

// hlServicesService is the JSON structure for a service in the Hostlists
// Registry.
type hlServicesService struct {
	ID      string   `json:"id"`
	Name    string   `json:"name"`
	IconSVG string   `json:"icon_svg"`
	Rules   []string `json:"rules"`
}
