package main

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha512"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"strings"
	"time"
)

var loginSalt string

type loginChecker struct {
	students   []Student
	authMethod AuthMethod
}

type loginUpload struct {
	Username string
	Password string
}


func (l loginChecker) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if addr := r.Header.Get("X-Forwarded-For"); addr != "" {
		r.RemoteAddr = addr
	}
	log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent())

	w.Header().Set("Content-Type", "text/plain")

	// Check request type and size
	if r.Method != "POST" {
		http.Error(w,
			"Invalid request",
			http.StatusBadRequest)
		return
	} else if r.ContentLength < 0 || r.ContentLength > 1023 {
		http.Error(w,
			"Request entity too large",
			http.StatusRequestEntityTooLarge)
		return
	}

	dec := json.NewDecoder(r.Body)
	var lu loginUpload
	if err := dec.Decode(&lu); err != nil {
		http.Error(w,
			err.Error(),
			http.StatusBadRequest)
		return
	}

	// Perform login check
	canContinue := false
	for _, std := range l.students {
		if std.Login == lu.Username {
			canContinue = true
		}
	}

	if !canContinue {
		log.Println("Login not found:", lu.Username, "at", r.RemoteAddr)
		http.Error(w, "Login not found in whitelist.", http.StatusUnauthorized)
		return
	}

	if ok, err := l.authMethod.checkAuth(lu.Username, lu.Password); err != nil {
		log.Println("Unable to perform authentication for", lu.Username, ":", err, "at", r.RemoteAddr)
		http.Error(w, err.Error(), http.StatusUnauthorized)
		return
	} else if !ok {
		log.Println("Login failed:", lu.Username, "at", r.RemoteAddr)
		http.Error(w, "Invalid password", http.StatusUnauthorized)
		return
	}

	// Find corresponding MAC
	var ip net.IP
	spl := strings.SplitN(r.RemoteAddr, ":", 2)
	if ip = net.ParseIP(spl[0]); ip == nil {
		http.Error(w, "Unable to parse given IPv4: " + spl[0], http.StatusInternalServerError)
		return
	}
	var mac *ARPEntry
	if tab, err := ARPAnalyze(); err != nil {
		log.Println("Error on ARPAnalyze:", err)
		http.Error(w, "Internal server error. Please retry in a few minutes", http.StatusInternalServerError)
		return
	} else {
		mac = ARPContainsIP(tab, ip)
	}

	if mac == nil {
		log.Printf("Unable to find MAC address for given IP (%s)\n", ip)
		http.Error(w, "Internal server error. Please retry in a few minutes", http.StatusInternalServerError)
		return
	}

	// Register the user remotely
	if ip, err := l.registerUser(lu.Username, r.RemoteAddr, *mac); err != nil {
		log.Println("Error on remote registration for", lu.Username, ":", err)
		http.Error(w, "Internal server error. Please retry in a few minutes", http.StatusInternalServerError)
		return
	} else if err := l.lateLoginAction(lu.Username, r.RemoteAddr, *mac, ip); err != nil {
		log.Println("Error on late login action for", lu.Username, ":", err)
		http.Error(w, "Internal server error. Please retry in a few minutes", http.StatusInternalServerError)
		return
	} else {
		log.Println("Successful login of", lu.Username, "at", r.RemoteAddr)
		http.Error(w, fmt.Sprintf("Use the following IP: %s", ip), http.StatusOK)
	}
}

type myIP struct {
	Id    int64     `json:"id"`
	Login string    `json:"login"`
	IP    string    `json:"ip"`
}

func (l loginChecker) registerUser(username, remoteAddr string, ent ARPEntry) (net.IP, error) {
	bts, err := json.Marshal(map[string]interface{}{"login": username, "ip": remoteAddr, "mac": fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x", ent.HWAddress[0], ent.HWAddress[1], ent.HWAddress[2], ent.HWAddress[3], ent.HWAddress[4], ent.HWAddress[5])})
	if err != nil {
		return nil, nil
	}

	req, err := http.NewRequest("POST", "https://adlin.nemunai.re/api/students/", bytes.NewReader(bts))
	if err != nil {
		return nil, err
	}
	req.Header.Add("X-ADLIN-Authentication", base64.StdEncoding.EncodeToString(hmac.New(sha512.New, []byte(loginSalt)).Sum([]byte(fmt.Sprintf("%d", time.Now().Unix()/10)))))
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(resp.Status)
	} else {
		dec := json.NewDecoder(resp.Body)
		var myip myIP
		if err := dec.Decode(&myip); err != nil {
			return nil, err
		}
		return net.ParseIP(myip.IP), nil
	}
}

func (l loginChecker) lateLoginAction(username, remoteAddr string, mac ARPEntry, ip net.IP) error {
	return RegisterUserMAC(mac, ip, username)
}
