package app

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"infinite-noodle/internal/noodle"
	"infinite-noodle/internal/web"

	"inet.af/tcpproxy"
)

type Config struct {
	DataPath string
	Host     string
	Port     int
	RunTest  bool
}

func Run(cfg Config) error {
	listenAddr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
	noodleChannel := make(chan noodle.Noodle)

	db := noodle.NewDatabase(cfg.DataPath)
	defer db.Close()

	if cfg.RunTest {
		runTestSequence(db)
	}

	go systemCheck(db, noodleChannel)
	go tcpProxify(noodleChannel)

	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(web.StaticFiles()))))
	http.HandleFunc("/", web.HandleMain(db, &noodleChannel))
	http.HandleFunc("/delete", web.HandleDelete(db, &noodleChannel))

	log.Printf("Server starting on %s", listenAddr)
	return http.ListenAndServe(listenAddr, nil)
}

func systemCheck(db *noodle.Database, noodleChannel chan noodle.Noodle) {
	for {
		noodles := db.GetAll()
		for _, item := range noodles {
			noodleChannel <- item
		}
		time.Sleep(60 * time.Second)
	}
}

func startProxy(proxy *tcpproxy.Proxy) {
	log.Print(proxy.Run())
}

func tcpProxify(noodleChannel chan noodle.Noodle) {
	noodleMap := make(map[string]*tcpproxy.Proxy)
	for {
		item := <-noodleChannel
		_, running := noodleMap[item.Id]
		if item.IsUp && !running {
			var proxy tcpproxy.Proxy
			src := fmt.Sprintf("0.0.0.0:%d", item.ListenPort)
			dst := fmt.Sprintf("%s:%d", item.DestHost, item.DestPort)
			log.Printf("Starting a noodle from %s to %s", src, dst)
			proxy.AddRoute(src, tcpproxy.To(dst))
			noodleMap[item.Id] = &proxy
			go startProxy(&proxy)
			continue
		}

		if !item.IsUp && running {
			log.Printf("Closing noodle=%v", item)
			if err := noodleMap[item.Id].Close(); err != nil {
				log.Print(err)
			}
		}
	}
}

func runTestSequence(db *noodle.Database) {
	for i := 0; i < 21; i++ {
		item := noodle.Noodle{
			Id:         db.MakeID(),
			Name:       "Name_Test",
			Proto:      "Proto_Test",
			ListenPort: 1080 + i,
			DestPort:   22,
			DestHost:   "localhost",
			Expiration: time.Now().Second(),
			IsUp:       true,
		}
		log.Printf("Test noodle=%v", item)
		db.Add(item)
		log.Printf("Test id=%s exists=%t", item.Id, db.Handle.Has(item.Id))
		log.Printf("Test GetAllGeneric=%v", db.GetAllGeneric())
	}
}
