package groups

import (
	"encoding/json"
	"fmt"

	"github.com/apex/log"
	"github.com/ooni/probe-cli/internal/database"
	"github.com/ooni/probe-cli/nettests"
	"github.com/ooni/probe-cli/nettests/im"
	"github.com/ooni/probe-cli/nettests/middlebox"
	"github.com/ooni/probe-cli/nettests/performance"
	"github.com/ooni/probe-cli/nettests/websites"
)

// NettestGroup base structure
type NettestGroup struct {
	Label    string
	Nettests []nettests.Nettest
	Summary  database.ResultSummaryFunc
}

// PerformanceSummary is the result summary for a performance test
type PerformanceSummary struct {
	Upload   int64
	Download int64
	Ping     float64
	Bitrate  int64
}

// MiddleboxSummary is the summary for the middlebox tests
type MiddleboxSummary struct {
	Detected bool
}

// IMSummary is the summary for the im tests
type IMSummary struct {
	Tested  uint
	Blocked uint
}

// WebsitesSummary is the summary for the websites test
type WebsitesSummary struct {
	Tested  uint
	Blocked uint
}

func checkRequiredKeys(rk []string, m database.SummaryMap) error {
	for _, key := range rk {
		if _, ok := m[key]; ok {
			continue
		}
		return fmt.Errorf("missing SummaryMap key '%s'", key)
	}
	return nil
}

// NettestGroups that can be run by the user
var NettestGroups = map[string]NettestGroup{
	"websites": NettestGroup{
		Label: "Websites",
		Nettests: []nettests.Nettest{
			websites.WebConnectivity{},
		},
		Summary: func(m database.SummaryMap) (string, error) {
			if err := checkRequiredKeys([]string{"WebConnectivity"}, m); err != nil {
				log.WithError(err).Error("missing keys")
				return "", err
			}

			// XXX to generate this I need to create the summary map as a list
			var summary WebsitesSummary
			summary.Tested = 0
			summary.Blocked = 0
			for _, msmtSummaryStr := range m["WebConnectivity"] {
				var wcSummary websites.WebConnectivitySummary

				err := json.Unmarshal([]byte(msmtSummaryStr), &wcSummary)
				if err != nil {
					log.WithError(err).Error("failed to unmarshal WebConnectivity summary")
					return "", err
				}
				if wcSummary.Blocked {
					summary.Blocked++
				}
				summary.Tested++
			}
			summaryBytes, err := json.Marshal(summary)
			if err != nil {
				return "", err
			}
			return string(summaryBytes), nil
		},
	},
	"performance": NettestGroup{
		Label: "Performance",
		Nettests: []nettests.Nettest{
			performance.Dash{},
			performance.NDT{},
		},
		Summary: func(m database.SummaryMap) (string, error) {
			if err := checkRequiredKeys([]string{"Dash", "Ndt"}, m); err != nil {
				log.WithError(err).Error("missing keys")
				return "", err
			}

			var (
				err         error
				ndtSummary  performance.NDTSummary
				dashSummary performance.DashSummary
				summary     PerformanceSummary
			)
			err = json.Unmarshal([]byte(m["Dash"][0]), &dashSummary)
			if err != nil {
				log.WithError(err).Error("failed to unmarshal Dash summary")
				return "", err
			}
			err = json.Unmarshal([]byte(m["Ndt"][0]), &ndtSummary)
			if err != nil {
				log.WithError(err).Error("failed to unmarshal NDT summary")
				return "", err
			}
			summary.Bitrate = dashSummary.Bitrate
			summary.Download = ndtSummary.Download
			summary.Upload = ndtSummary.Upload
			summary.Ping = ndtSummary.AvgRTT
			summaryBytes, err := json.Marshal(summary)
			if err != nil {
				return "", err
			}
			return string(summaryBytes), nil
		},
	},
	"middlebox": NettestGroup{
		Label: "Middleboxes",
		Nettests: []nettests.Nettest{
			middlebox.HTTPInvalidRequestLine{},
			middlebox.HTTPHeaderFieldManipulation{},
		},
		Summary: func(m database.SummaryMap) (string, error) {
			if err := checkRequiredKeys([]string{"WebConnectivity"}, m); err != nil {
				log.WithError(err).Error("missing keys")
				return "", err
			}

			var (
				err         error
				hhfmSummary middlebox.HTTPHeaderFieldManipulationSummary
				hirlSummary middlebox.HTTPInvalidRequestLineSummary
				summary     MiddleboxSummary
			)
			err = json.Unmarshal([]byte(m["HttpHeaderFieldManipulation"][0]), &hhfmSummary)
			if err != nil {
				log.WithError(err).Error("failed to unmarshal hhfm summary")
				return "", err
			}
			err = json.Unmarshal([]byte(m["HttpInvalidRequestLine"][0]), &hirlSummary)
			if err != nil {
				log.WithError(err).Error("failed to unmarshal hirl summary")
				return "", err
			}
			summary.Detected = hirlSummary.Tampering == true || hhfmSummary.Tampering == true
			summaryBytes, err := json.Marshal(summary)
			if err != nil {
				return "", err
			}
			return string(summaryBytes), nil
		},
	},
	"im": NettestGroup{
		Label: "Instant Messaging",
		Nettests: []nettests.Nettest{
			im.FacebookMessenger{},
			im.Telegram{},
			im.WhatsApp{},
		},
		Summary: func(m database.SummaryMap) (string, error) {
			if err := checkRequiredKeys([]string{"Whatsapp", "Telegram", "FacebookMessenger"}, m); err != nil {
				log.WithError(err).Error("missing keys")
				return "", err
			}
			var (
				err       error
				waSummary im.WhatsAppSummary
				tgSummary im.TelegramSummary
				fbSummary im.FacebookMessengerSummary
				summary   IMSummary
			)
			err = json.Unmarshal([]byte(m["Whatsapp"][0]), &waSummary)
			if err != nil {
				log.WithError(err).Error("failed to unmarshal whatsapp summary")
				return "", err
			}
			err = json.Unmarshal([]byte(m["Telegram"][0]), &tgSummary)
			if err != nil {
				log.WithError(err).Error("failed to unmarshal telegram summary")
				return "", err
			}
			err = json.Unmarshal([]byte(m["FacebookMessenger"][0]), &fbSummary)
			if err != nil {
				log.WithError(err).Error("failed to unmarshal facebook summary")
				return "", err
			}
			// XXX it could actually be that some are not tested when the
			// configuration is changed.
			summary.Tested = 3
			summary.Blocked = 0
			if fbSummary.Blocked == true {
				summary.Blocked++
			}
			if tgSummary.Blocked == true {
				summary.Blocked++
			}
			if waSummary.Blocked == true {
				summary.Blocked++
			}

			summaryBytes, err := json.Marshal(summary)
			if err != nil {
				return "", err
			}
			return string(summaryBytes), nil
		},
	},
}