package main

import (
	"fmt"
	"log"
	"net"

	"github.com/Queueue0/qpass/internal/crypto"
	"github.com/Queueue0/qpass/internal/dbman"
	"github.com/Queueue0/qpass/internal/models"
	"github.com/Queueue0/qpass/internal/protocol"
)

type Application struct {
	users     *models.UserModel
	passwords *models.PasswordModel
	homeDir   string
}

func main() {
	qpassHome, err := dbman.GetQpassServerHome()
	if err != nil {
		log.Fatal(err)
	}

	dsn := fmt.Sprintf("file:%s/pwdb.sqlite?mode=rwc", qpassHome)
	db, err := dbman.OpenDB(dsn)
	if err != nil {
		log.Fatal(err)
	}

	defer db.Close()

	err = dbman.InitializeDB(db, false)
	if err != nil {
		log.Fatal(err)
	}

	if !haveKeys(qpassHome) {
		err = genKeyPair(qpassHome)
		if err != nil {
			log.Fatal(err)
		}
	}

	um := models.UserModel{
		DB: db,
	}

	pm := models.PasswordModel{
		DB: db,
	}

	a := Application{
		users:     &um,
		passwords: &pm,
		homeDir:   qpassHome,
	}

	srv, err := net.Listen("tcp", "127.0.0.1:10448")
	if err != nil {
		panic(err)
	}
	defer srv.Close()

	log.Println(fmt.Sprintf("Server started on %s", srv.Addr().String()))

	for {
		c, err := srv.Accept()
		if err != nil {
			panic(err)
		}

		go a.handle(c)
	}
}

func (app *Application) handle(c net.Conn) {
	log.Println("Received connection", c.RemoteAddr().String())
	kp, err := getKeyPair(app.homeDir)
	if err != nil {
		log.Println(err.Error())
		return
	}

	sc, err := crypto.NewServerConn(c, kp.key, kp.pubKey)
	if err != nil {
		log.Println(c.RemoteAddr(), err.Error())
		return
	}

	app.respond(sc)
}

const (
	authFail   = "Auth Failure"
	notAuthed  = "Not Authenticated"
)

func (app *Application) respond(c net.Conn) {
	defer c.Close()
	authenticated := false

connLoop:
	for {
		var p protocol.Payload
		_, err := p.ReadFrom(c)
		if err != nil {
			log.Println(c.RemoteAddr(), err.Error())
			return
		}

		log.Println(c.RemoteAddr(), p.TypeString())

		switch p.Type() {
		case protocol.PING:
			protocol.NewPong().WriteTo(c)
		case protocol.AUTH:
			if authenticated {
				authenticated = false
				protocol.NewFail(authFail).WriteTo(c)
				continue
			}
			var id string
			authenticated, id, err = app.authenticate(p)
			if err != nil {
				protocol.NewFail(authFail).WriteTo(c)
				log.Println(c.RemoteAddr(), err.Error())
				continue
			}

			if authenticated {
				protocol.NewSuccWithData([]byte(id)).WriteTo(c)
			} else {
				protocol.NewFail(authFail).WriteTo(c)
			}
		case protocol.SYNC:
			if !authenticated {
				protocol.NewFail(notAuthed).WriteTo(c)
			}
			app.sync(p, c)
		case protocol.NUSR:
			app.newUser(p, c)
		case protocol.SUCC:
			break connLoop
		}
	}
}