package main

import (
	"crypto/md5"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"code.justin.tv/common/golibs/statsd"

	"goji.io"
	"goji.io/pat"
	"golang.org/x/net/context"
)

var hostports struct {
	WWWHostport       string
	WebClientHostport string
	CDNHostport       string
	APIHostport       string
}

const (
	title    = "ember"
	globList = "*.js,*.css,*.html,styles/*.css,translations/*.js"
	host     = "Host"
	ref      = "Referer"
	forward  = "X-Forwarded-For"
	ua       = "User-Agent"
	lb       = "X-Ttvlb-Client-Ip"
)

type route struct {
	pattern *pat.Pattern
	handler goji.HandlerFunc
}

var (
	env               = "development"
	port              = 80
	wwwHostport       = "www.twitch.tv"
	cdnHostport       = "www-cdn.jtvnw.net"
	webAssetHostport  = "www-origin.justin.tv"
	webClientHostport = "web-cdn.ttvnw.net"
	apiHostport       = "api.twitch.tv"
	blacklist         = map[string]bool{
		"game-commerce.js": true,
	}
	fingerprintRegex = regexp.MustCompile("-[a-f0-9]{32}\\.")
)

type gzipResponseWriter struct {
	io.Writer
	http.ResponseWriter
}

func (w gzipResponseWriter) Write(b []byte) (int, error) {
	return w.Writer.Write(b)
}

func easyRand(s int, e int) int {
	return s + rand.Intn(e-s+1)
}

func overrideFromEnv(variable *string, environmentVarName string) {
	envValue := os.Getenv(environmentVarName)
	if envValue != "" {
		*variable = envValue
	}
}

func main() {
	flag.IntVar(&port, "port", port, "port to listen on")
	flag.StringVar(&wwwHostport, "www-hostport", wwwHostport, "Hostport prefix to use for twitch web resources")
	flag.StringVar(&cdnHostport, "cdn-hostport", cdnHostport, "Hostport prefix to use for www-cdn (rails) assets")
	flag.StringVar(&webAssetHostport, "web-asset-hostport", webAssetHostport, "Hostport prefix to use to proxy assets")
	flag.StringVar(&webClientHostport, "webclient-hostport", webClientHostport, "Hostport prefix to use for web-cdn (ember) assets")
	flag.StringVar(&apiHostport, "api-hostport", apiHostport, "Hostport prefix to use for api requests")
	flag.Parse()

	overrideFromEnv(&env, "ENV")
	overrideFromEnv(&wwwHostport, "WWW_HOSTPORT")
	overrideFromEnv(&cdnHostport, "CDN_HOSTPORT")
	overrideFromEnv(&apiHostport, "API_HOSTPORT")
	overrideFromEnv(&webClientHostport, "WEBCLIENT_HOSTPORT")
	overrideFromEnv(&apiHostport, "API_HOSTPORT")
	log.Printf("Launching in the %s environment...", env)

	hostports.WWWHostport = "//" + wwwHostport
	hostports.WebClientHostport = "//" + webClientHostport
	hostports.CDNHostport = "//" + cdnHostport
	hostports.APIHostport = "//" + apiHostport
	log.Printf("loading with these remotes: %+v", hostports)

	InitMetaTags("https:" + hostports.APIHostport)

	mux := goji.NewMux()
	mux.UseC(requestLogger)
	addRoutes(mux)

	log.Printf("Starting server on port %d...", port)
	err := http.ListenAndServe(fmt.Sprintf(":%d", port), mux)
	if err != nil {
		log.Fatal("ListenAndServe failed: ", err)
	}
}

func addRoutes(mux *goji.Mux) {
	globs := strings.Split(globList, ",")
	files := []string{}

	stats := configureStatsd()

	routes := []route{}

	for _, glob := range globs {
		globfiles, err := filepath.Glob(glob)
		if err != nil {
			log.Fatalf("Glob for %s failed: %v", glob, err)
		}

		files = append(files, globfiles...)
	}

	for _, file := range files {
		if blacklist[file] {
			log.Printf("Not serving blacklisted file %s", file)
			continue
		}
		ext := filepath.Ext(file)
		name := strings.TrimSuffix(file, ext)

		content, err := ioutil.ReadFile(file)
		if err != nil {
			log.Fatalf("ReadFile for %s failed: %v", file, err)
		}

		checksum := md5.Sum(content)

		alias := fmt.Sprintf("/%s%s", name, ext)
		actual := fmt.Sprintf("/%s-%x%s", name, checksum, ext)

		if fingerprintRegex.MatchString(alias) {
			// If the file already has a fingerprint, don't try to fingerprint it again
			routes = append(routes, route{
				pattern: pat.Get(alias),
				handler: serve(file, alias, string(content), stats),
			})
			log.Printf("Serving: " + alias)
		} else {
			routes = append(routes, route{
				pattern: pat.Get(alias),
				handler: redirect(file, alias, actual, stats),
			})
			routes = append(routes, route{
				pattern: pat.Get(actual),
				handler: serve(file, actual, string(content), stats),
			})
			log.Printf("Serving: " + alias)
			log.Printf("Serving: " + actual)
		}
	}

	routes = append(routes, []route{
		{
			pattern: pat.Get("/images/*"),
			handler: assetProxy(webAssetHostport, stats),
		},
		{
			pattern: pat.Get("/webfonts/*"),
			handler: assetProxy(webAssetHostport, stats),
		},
		{
			pattern: pat.Get("/health"),
			handler: elbHealthCheck,
		},
		{
			pattern: pat.Get("/"),
			handler: errorHandler,
		},
		{
			pattern: pat.Get("/:login"),
			handler: channelPage,
		},
	}...)

	for _, r := range routes {
		mux.HandleFuncC(r.pattern, r.handler)
	}
}

func requestLogger(h goji.Handler) goji.Handler {
	return goji.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
		log.Printf("%s: %s\t%s: %s\t%s: %s\t%s: %s\t%s: %s",
			host, r.Header.Get(host),
			ref, r.Header.Get(ref),
			forward, r.Header.Get(forward),
			ua, r.Header.Get(ua),
			lb, r.Header.Get(lb))

		h.ServeHTTPC(ctx, w, r)
	})
}

func configureStatsd() statsd.Stats {
	statsdHostport := "statsd.internal.justin.tv:8125" //os.Getenv("STATSD_HOSTPORT")
	var err error
	var stats statsd.Stats
	if statsdHostport == "" {
		stats = statsd.Noop()
	} else {
		if stats, err = statsd.Dial("udp", statsdHostport,
			statsd.StatsConfig{Rate: 1.0, Prefix: fmt.Sprintf("%s.%s", title, env)}); err != nil {
			log.Fatalf("StatsD configuration error: %v", err)
		}
		log.Printf("Connected to StatsD at %s\n", statsdHostport)
	}
	return stats
}
