package cli

import (
	"encoding/json"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/apex/log"
	"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/database"
	"github.com/ooni/probe-cli/v3/cmd/ooniprobe/internal/utils"
)

func formatSpeed(speed float64) string {
	if speed < 1000 {
		return fmt.Sprintf("%.2f Kbit/s", speed)
	} else if speed < 1000*1000 {
		return fmt.Sprintf("%.2f Mbit/s", float32(speed)/1000)
	} else if speed < 1000*1000*1000 {
		return fmt.Sprintf("%.2f Gbit/s", float32(speed)/(1000*1000))
	}
	// WTF, you crazy?
	return fmt.Sprintf("%.2f Tbit/s", float32(speed)/(1000*1000*1000))
}

func formatSize(size float64) string {
	if size < 1024 {
		return fmt.Sprintf("%.1fK", size)
	} else if size < 1024*1024 {
		return fmt.Sprintf("%.1fM", size/1024.0)
	} else if size < 1024*1024*1024 {
		return fmt.Sprintf("%.1fG", size/(1024.0*1024.0))
	}
	// WTF, you crazy?
	return fmt.Sprintf("%.1fT", size/(1024*1024*1024))
}

var summarizers = map[string]func(uint64, uint64, string) []string{
	"websites": func(totalCount uint64, anomalyCount uint64, ss string) []string {
		return []string{
			fmt.Sprintf("%d tested", totalCount),
			fmt.Sprintf("%d blocked", anomalyCount),
			"",
		}
	},
	"performance": func(totalCount uint64, anomalyCount uint64, ss string) []string {
		var tk database.PerformanceTestKeys
		if err := json.Unmarshal([]byte(ss), &tk); err != nil {
			return nil
		}
		return []string{
			fmt.Sprintf("Download: %s", formatSpeed(tk.Download)),
			fmt.Sprintf("Upload: %s", formatSpeed(tk.Upload)),
			fmt.Sprintf("Ping: %.2fms", tk.Ping),
		}
	},
	"im": func(totalCount uint64, anomalyCount uint64, ss string) []string {
		return []string{
			fmt.Sprintf("%d tested", totalCount),
			fmt.Sprintf("%d blocked", anomalyCount),
			"",
		}
	},
	"middlebox": func(totalCount uint64, anomalyCount uint64, ss string) []string {
		return []string{
			fmt.Sprintf("Detected: %v", anomalyCount > 0),
			"",
			"",
		}
	},
	"circumvention": func(totalCount uint64, anomalyCount uint64, ss string) []string {
		return []string{
			fmt.Sprintf("%d tested", totalCount),
			fmt.Sprintf("%d blocked", anomalyCount),
			"",
		}
	},
	"experimental": func(totalCount uint64, anomalyCount uint64, ss string) []string {
		return []string{
			fmt.Sprintf("%d tested", totalCount),
			fmt.Sprintf("%d blocked", anomalyCount),
			"",
		}
	},
}

func makeSummary(name string, totalCount uint64, anomalyCount uint64, ss string) []string {
	return summarizers[name](totalCount, anomalyCount, ss)
}

func logResultItem(w io.Writer, f log.Fields) error {
	colWidth := 24
	rID := f.Get("id").(int64)
	name := f.Get("name").(string)
	isDone := f.Get("is_done").(bool)
	startTime := f.Get("start_time").(time.Time)
	networkName := f.Get("network_name").(string)
	asn := fmt.Sprintf("AS%d (%s)", f.Get("asn").(uint), f.Get("network_country_code").(string))
	//runtime := f.Get("runtime").(float64)
	//dataUsageUp := f.Get("dataUsageUp").(int64)
	//dataUsageDown := f.Get("dataUsageDown").(int64)
	index := f.Get("index").(int)
	totalCount := f.Get("total_count").(int)
	if index == 0 {
		fmt.Fprintf(w, "┏"+strings.Repeat("━", colWidth*2+2)+"┓\n")
	} else {
		fmt.Fprintf(w, "┢"+strings.Repeat("━", colWidth*2+2)+"┪\n")
	}

	firstRow := utils.RightPad(fmt.Sprintf("#%d - %s", rID, startTime.Format(time.RFC822)), colWidth*2)
	fmt.Fprintf(w, "┃ "+firstRow+" ┃\n")
	fmt.Fprintf(w, "┡"+strings.Repeat("━", colWidth*2+2)+"┩\n")

	summary := makeSummary(name,
		f.Get("measurement_count").(uint64),
		f.Get("measurement_anomaly_count").(uint64),
		f.Get("test_keys").(string))

	fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n",
		utils.RightPad(name, colWidth),
		utils.RightPad(summary[0], colWidth)))
	fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n",
		utils.RightPad(networkName, colWidth),
		utils.RightPad(summary[1], colWidth)))
	fmt.Fprintf(w, fmt.Sprintf("│ %s %s│\n",
		utils.RightPad(asn, colWidth),
		utils.RightPad(summary[2], colWidth)))

	if index == totalCount-1 {
		if isDone == true {
			fmt.Fprintf(w, "└┬──────────────┬─────────────┬───────────────────┬┘\n")
		} else {
			// We want the incomplete section to not have a footer
			fmt.Fprintf(w, "└──────────────────────────────────────────────────┘\n")
		}
	}
	return nil
}

func logResultSummary(w io.Writer, f log.Fields) error {

	networks := f.Get("total_networks").(int64)
	tests := f.Get("total_tests").(int64)
	dataUp := f.Get("total_data_usage_up").(float64)
	dataDown := f.Get("total_data_usage_down").(float64)
	if tests == 0 {
		fmt.Fprintf(w, "No results\n")
		fmt.Fprintf(w, "Try running:\n")
		fmt.Fprintf(w, "  ooniprobe run websites\n")
		return nil
	}
	//              └┬──────────────┬─────────────┬───────────────┬
	fmt.Fprintf(w, " │ %s │ %s │ %s │\n",
		utils.RightPad(fmt.Sprintf("%d tests", tests), 12),
		utils.RightPad(fmt.Sprintf("%d nets", networks), 11),
		utils.RightPad(fmt.Sprintf("⬆ %s  ⬇ %s", formatSize(dataUp), formatSize(dataDown)), 17))
	fmt.Fprintf(w, " └──────────────┴─────────────┴───────────────────┘\n")

	return nil
}