package qrz

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"regexp"
	"strings"

	"git.paulbsd.com/paulbsd/qrz/src/config"
	"github.com/antchfx/htmlquery"
	"github.com/robfig/cron"
	"golang.org/x/net/html"
)

const gxpath = "//center/form/select/*"
const agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"

var frsre = regexp.MustCompile(`^   [0-9]{1,4}\s[A-Z]{1,5}\s[0-9]{1,5}`)
var gre1 = regexp.MustCompile(`.*document.write\('(.*)'\).*`)
var gre2 = regexp.MustCompile(`.*_(\d{1,3})\.php$`)

// InitCronConfig create task schedules
func InitCronConfig(config config.Config) (err error) {
	cr := cron.New()
	cr.AddFunc(config.Cron, func() { err = Run(config) })
	cr.Start()
	return
}

// Run runs the main loop
func Run(config config.Config) (err error) {
	existingQrz, err := getCurrentEntries(config)
	if err != nil {
		log.Fatalln(err)
	}

	groups, err := getGroups(config.URLBaseForGroups)

	for _, group := range groups {
		url := fmt.Sprintf(config.URLBase, group)
		log.Printf("Processing the %s group with URL %s",
			group,
			url)

		bodyStr, err := getBody(url)
		if err != nil {
			log.Println(err)
			break
		}

		frsPeople, err := getFrsEntries(config, bodyStr)
		if err != nil {
			log.Println(err)
			break
		}

		err = discardExistingEntries(config, &frsPeople, existingQrz)
		if err != nil {
			log.Println(err)
			continue
		}

		if len(frsPeople) > 0 {
			err = insertFrsEntryToDB(config, frsPeople)
			if err != nil {
				log.Println(err)
				continue
			}
		} else {
			log.Printf("No entry to insert in database for group %s",
				group)
		}
	}
	return
}

func getGroups(urlbase string) (groups []string, err error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", urlbase, nil)
	req.Header.Set("User-Agent", agent)

	resp, err := client.Do(req)
	if err != nil {
		log.Println(err)
	}
	pagebody, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Println(err)
	}
	retstr := string(pagebody)

	var body string
	res := gre1.FindStringSubmatch(retstr)
	if len(res) > 0 {
		body = res[1]
	} else {
		log.Printf("error getting body for url %s\n", urlbase)
		return
	}

	htmlpage, err := htmlquery.Parse(strings.NewReader(body))
	if err != nil {
		return
	}

	query := htmlquery.Find(htmlpage, gxpath)

	for _, opt := range query {
		rematch := gre2.FindStringSubmatch(opt.Attr[0].Val)
		if len(rematch) > 1 {
			groups = append(groups, rematch[1])
		}
	}

	return
}

// getBody fetch html page
func getBody(url string) (string, error) {
	client := &http.Client{}
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("User-Agent", agent)

	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	/*
		// For old website support 23/05/2020
		dec := charmap.ISO8859_1.NewDecoder()
		output, _ := dec.Bytes(body)
		bodyString := string(output)
	*/
	return string(body), nil
}

// getFrsEntries get FRS entries from html body
func getFrsEntries(config config.Config, body string) (frsentries map[string]Qrz, err error) {
	frsentries = make(map[string]Qrz)

	htmlpage, err := htmlquery.Parse(strings.NewReader(body))
	if err != nil {
		return
	}

	for _, n := range htmlquery.Find(htmlpage, "//tr") {
		td := htmlquery.Find(n, "//td")

		if len(td) >= 6 {
			if frsre.MatchString(htmlquery.InnerText(td[1])) {
				frs := Qrz{
					DMRID:   getColumn(td[0]),
					QRZ:     getColumn(td[1]),
					Name:    getColumn(td[2]),
					City:    getColumn(td[3]),
					Dept:    getColumn(td[4]),
					Country: getColumn(td[5])}
				frsentries[frs.QRZ] = frs
			} else if frsre.MatchString(htmlquery.InnerText(td[0])) {
				frs := Qrz{
					QRZ:     getColumn(td[0]),
					Name:    getColumn(td[1]),
					City:    getColumn(td[2]),
					Dept:    getColumn(td[3]),
					Country: getColumn(td[4])}
				frsentries[frs.QRZ] = frs
			}
		}
	}

	return
}

func getColumn(elem *html.Node) (ret string) {
	ret = strings.TrimLeft(htmlquery.InnerText(elem), " ")
	return
}

// getCurrentEntries fetch existing entries from database
func getCurrentEntries(config config.Config) (qrz []Qrz, err error) {
	err = config.Db.Find(&qrz)

	return
}

// discardExistingEntries remove existing entries from original map[string]Qrz
func discardExistingEntries(config config.Config, frsPeople *map[string]Qrz, existingQRZ []Qrz) (err error) {
	for _, entry := range existingQRZ {
		delete(*frsPeople, entry.QRZ)
	}

	return
}

// insertFrsEntryToDB inserts frs entries to database
func insertFrsEntryToDB(config config.Config, frsPeople map[string]Qrz) (err error) {
	session := config.Db.NewSession()
	defer session.Close()

	err = session.Begin()

	var qrzNum int

	log.Printf("Starting insert of %d entries",
		len(frsPeople))

	for _, frs := range frsPeople {
		_, err = session.Insert(&frs)
		if err != nil {
			log.Println(err)
		}
		qrzNum++
		if (qrzNum % 5) == 0 {
			err = session.Commit()
			if err != nil {
				return err
			}
		}
	}

	err = session.Commit()
	if err != nil {
		return err
	}
	log.Printf("Committed %d qrz entries", qrzNum)

	return nil
}

// ToSlice converts struct to array
/* func ToSlice(qrz Qrz) (out []interface{}) {
	val := reflect.ValueOf(qrz)
	num := val.NumField()

	for i := 1; i < num-1; i++ {
		out = append(out, val.Field(i).Interface())
	}
	return
} */

// ToSlice converts struct to array
func ToSlice(qrz Qrz) (out []string) {
	out = []string{
		qrz.QRZ,
		qrz.Name,
		qrz.City,
		qrz.Dept,
		qrz.Country,
		qrz.DMRID}
	return
}

// Qrz describe FRS people
type Qrz struct {
	ID      int    `db:"id" xorm:"pk autoincr"`
	QRZ     string `db:"qrz" xorm:"index notnull"`
	DMRID   string `db:"dmrid" xorm:"index notnull"`
	Name    string `db:"name" xorm:"index notnull"`
	Address string `db:"address" xorm:"index notnull"`
	City    string `db:"city" xorm:"index notnull"`
	Zipcode string `db:"zipcode" xorm:"index varchar(5) notnull"`
	Dept    string `db:"dept" xorm:"index notnull"`
	Country string `db:"country" xorm:"index notnull"`
}
