package main

import (
	"context"
	"expvar"
	"flag"
	"fmt"
	"log"
	"os"
	"runtime"
	"strings"
	"time"

	"database/sql"
	"net/http"

	_ "github.com/lib/pq"
	"github.com/gorilla/websocket"
	apns "github.com/sideshow/apns2"
	"github.com/sideshow/apns2/token"

	"party.at/party/cmd/party/common"
	"party.at/party/internal/data"
	"party.at/party/internal/jsonlog"
	"party.at/party/internal/mailer"
	"party.at/party/cmd/party/parlament"
)

var version string
var buildTime string

type Message struct {
	Type      string  `json:"type"`
	Timestamp string  `json:"timestamp"`
	Random    float64 `json:"random"`
}

var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool { return true },
}

func main() {

	if len(os.Args) > 1 && os.Args[1] == "create-admin" {
		runCreateAdmin(os.Args[2:])
		return
	}
	if len(os.Args) > 1 && os.Args[1] == "send-test-notification" {
		runSendTestNotification(os.Args[2:])
		return
	}

	var cfg common.Config

	flag.IntVar(&cfg.Port, "port", 4100, "API server port")
	flag.StringVar(&cfg.Env, "env", "production", "Environment (development|staging|production)")
	flag.StringVar(&cfg.DB.DSN, "db-dsn", os.Getenv("PARTY_DB_DSN"), "PostgreSQL DSN")

	flag.IntVar(&cfg.DB.MaxOpenConns, "db-max-open-conns", 25, "PostgreSQL max open connections")
	flag.IntVar(&cfg.DB.MaxIdleConns, "db-max-idle-conns", 25, "PostgreSQL max idle connections")
	flag.StringVar(&cfg.DB.MaxIdleTime, "db-max-idle-time", "15m", "PostgreSQL max connection idle time")

	flag.StringVar(&cfg.SMTP.Host, "smtp-host", "smtp.mailtrap.io", "SMTP host")
	flag.IntVar(&cfg.SMTP.Port, "smtp-port", 25, "SMTP port")
	flag.StringVar(&cfg.SMTP.Username, "smtp-username", os.Getenv("SMTP_USERNAME"), "SMTP username")
	flag.StringVar(&cfg.SMTP.Password, "smtp-password", os.Getenv("SMTP_PASSWORD"), "SMTP password")
	flag.StringVar(&cfg.SMTP.Sender, "smtp-sender", "DPÖ <no-reply@party.at>", "SMTP sender")

	flag.Float64Var(&cfg.Limiter.RPS, "limiter-rps", 2, "Rate limiter maximum requests per second")
	flag.IntVar(&cfg.Limiter.Burst, "limiter-burst", 4, "Rate limiter maximum burst")
	flag.BoolVar(&cfg.Limiter.Enabled, "limiter-enabled", true, "Enable rate limiter")

	flag.Func("cors-trusted-origins", "Trusted CORS origins (space separated)", func(val string) error {
		cfg.CORS.TrustedOrigins = strings.Fields(val)
		return nil
	})

	flag.StringVar(&cfg.APNS.KeyPath, "apns-key-path", os.Getenv("APNS_KEY_PATH"), "Path to APNs .p8 auth key file")
	flag.StringVar(&cfg.APNS.KeyID, "apns-key-id", os.Getenv("APNS_KEY_ID"), "APNs key ID")
	flag.StringVar(&cfg.APNS.TeamID, "apns-team-id", os.Getenv("APNS_TEAM_ID"), "Apple Developer team ID")
	flag.StringVar(&cfg.APNS.BundleID, "apns-bundle-id", os.Getenv("APNS_BUNDLE_ID"), "iOS app bundle ID")

	displayVersion := flag.Bool("version", false, "Display version and exit")
	flag.Parse()

	if *displayVersion {
		fmt.Printf("Version:\t%s\n", version)
		fmt.Printf("Build time:\t%s\n", buildTime)
		os.Exit(0)
	}

	logger := jsonlog.New(os.Stdout, jsonlog.LevelInfo)

	log.Printf("%s\n", cfg.DB.DSN)

	db, err := openDB(cfg)
	if err != nil {
		logger.PrintFatal(err, nil)
	}
	defer db.Close()

	expvar.NewString("version").Set(version)
	expvar.Publish("goroutines", expvar.Func(func() any { return runtime.NumGoroutine() }))
	expvar.Publish("database", expvar.Func(func() any { return db.Stats() }))
	expvar.Publish("timestamp", expvar.Func(func() any { return time.Now().Unix() }))

	var apnsClient *apns.Client
	if cfg.APNS.KeyPath != "" && cfg.APNS.KeyID != "" && cfg.APNS.TeamID != "" {
		authKey, err := token.AuthKeyFromFile(cfg.APNS.KeyPath)
		if err != nil {
			logger.PrintFatal(fmt.Errorf("apns: %w", err), nil)
		}
		apnsClient = apns.NewTokenClient(&token.Token{
			AuthKey: authKey,
			KeyID:   cfg.APNS.KeyID,
			TeamID:  cfg.APNS.TeamID,
		}).Production()
	}

	app := &common.Application{
		Config:    cfg,
		Logger:    logger,
		Models:    data.NewModels(db),
		Mailer:    mailer.New(cfg.SMTP.Host, cfg.SMTP.Port, cfg.SMTP.Username, cfg.SMTP.Password, cfg.SMTP.Sender),
		Parlament: parlament.NewClient(),
		APNS:      apnsClient,
	}

	log.Println("Hello, Sailor!")

	startParlWatcher(app)

	if err = serve(app); err != nil {
		logger.PrintFatal(err, nil)
	}
}

func openDB(cfg common.Config) (*sql.DB, error) {
	db, err := sql.Open("postgres", cfg.DB.DSN)
	if err != nil {
		return nil, err
	}

	db.SetMaxOpenConns(cfg.DB.MaxOpenConns)
	db.SetMaxIdleConns(cfg.DB.MaxIdleConns)
	duration, err := time.ParseDuration(cfg.DB.MaxIdleTime)
	if err != nil {
		return nil, err
	}
	db.SetConnMaxIdleTime(duration)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err = db.PingContext(ctx); err != nil {
		return nil, err
	}
	return db, nil
}
