package brass

import (
	"fmt"
	"net/http"
	"strings"
	"time"

	"src.rybka.ca/pkg/util"
)

type Server[DataType any, UserDataType any, StateType any] struct {
	App    *App[DataType, UserDataType, StateType]
	Config *Config
	DB     *util.FileDB[Data[DataType]]
}

func (s *Server[DataType, UserDataType, StateType]) handler() http.Handler {
	mux := http.NewServeMux()
	mux.HandleFunc("/auth/login", s.handleLogin)
	mux.HandleFunc("/", s.handleHomeView)
	mux.HandleFunc("/main.js", s.handleMainJS)
	mux.HandleFunc("/main.css", s.handleMainCSS)
	return mux
}

func (s *Server[DataType, UserDataType, StateType]) getToken(r *http.Request) (string, bool) {
	authHeader := r.Header.Get("Authorization")
	if authHeader != "" {
		parts := strings.Split(authHeader, " ")
		if len(parts) != 2 {
			return "", false
		}
		return parts[1], true
	}
	c, err := r.Cookie("token")
	if err != nil {
		return "", false
	}
	return c.Value, true
}

func (s *Server[DataType, UserDataType, StateType]) getUser(r *http.Request) (string, string, bool, error) {
	token, ok := s.getToken(r)
	if !ok {
		return "", "", false, nil
	}
	phone := ""
	err := s.DB.Do(func(d *Data[DataType]) error {
		if d == nil {
			d = &Data[DataType]{}
		}
		if d.Sessions == nil {
			d.Sessions = map[string]string{}
		}
		phone = d.Sessions[token]
		return nil
	})
	if err != nil {
		return "", "", false, err
	}
	if phone == "" {
		return "", "", false, nil
	}
	return phone, token, true, nil
}

func (s *Server[DataType, UserDataType, StateType]) authenticate(w http.ResponseWriter, r *http.Request, do func(userID string)) {
	id, _, ok, err := s.getUser(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if !ok {
		loginPage(w, r, "", nil, nil)
		return
	}
	do(id)
}

func (s *Server[DataType, UserDataType, StateType]) handleLogin(w http.ResponseWriter, r *http.Request) {
	params, err := getParams(r)
	if err != nil {
		http.Error(w, "bad form", http.StatusBadRequest)
		return
	}
	phone := normalizePhone(params["phone"])
	err = validatePhone(phone)
	if err != nil {
		loginPage(w, r, phone, err, nil)
		return
	}
	code := params["code"]
	if code == "" {
		// Send login code
		err = s.sendLoginCode(phone)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		// Return login page
		loginPage(w, r, phone, nil, nil)
	} else {
		token, ok, err := s.login(phone, code)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		if !ok {
			loginPage(w, r, phone, nil, fmt.Errorf("invalid code"))
			return
		}
		if strings.HasPrefix(r.Header.Get("Accept"), "text/html") {
			http.SetCookie(w, &http.Cookie{
				Name:   "token",
				Value:  token,
				Secure: true,
				Path:   "/",
			})
			http.Redirect(w, r, "/", http.StatusSeeOther)
		} else {
			w.Write([]byte(token))
		}
	}
}

func (s *Server[DataType, UserDataType, StateType]) sendLoginCode(phone string) error {
	code, err := generateLoginCode()
	if err != nil {
		return err
	}
	err = s.DB.Do(func(d *Data[DataType]) error {
		if d == nil {
			d = &Data[DataType]{}
		}
		if d.LoginCodes == nil {
			d.LoginCodes = map[string]map[string]bool{}
		}
		if d.LoginCodes[phone] == nil {
			d.LoginCodes[phone] = map[string]bool{}
		}
		d.LoginCodes[phone][code] = true
		return nil
	})
	if err != nil {
		return err
	}

	go func() {
		time.Sleep(5 * time.Minute)
		err = s.DB.Do(func(d *Data[DataType]) error {
			if d == nil {
				d = &Data[DataType]{}
			}
			if d.LoginCodes == nil {
				d.LoginCodes = map[string]map[string]bool{}
			}
			if d.LoginCodes[phone] == nil {
				d.LoginCodes[phone] = map[string]bool{}
			}
			delete(d.LoginCodes[phone], code)
			return nil
		})
		if err != nil {
			s.logError(err)
		}
	}()

	msg := fmt.Sprintf("%s is your login code.", code)
	return s.Config.smsClient().Send(phone, msg)
}

func (s *Server[DataType, UserDataType, StateType]) login(phone, code string) (string, bool, error) {
	// Check if code is valid
	var ok bool
	err := s.DB.Do(func(d *Data) error {
		if d == nil {
			ok = false
			return nil
		}
		if d.Users == nil {
			ok = false
			return nil
		}
		if d.Users[phone] == nil {
			ok = false
			return nil
		}
		if d.Users[phone].LoginCodes == nil {
			ok = false
			return nil
		}
		ok = d.Users[phone].LoginCodes[code]
		return nil
	})
	if err != nil {
		return "", false, err
	}
	if !ok {
		return "", false, nil
	}

	// Generate a session token
	token := ""
	err = s.DB.Do(func(d *Data) error {
		if d.Sessions == nil {
			d.Sessions = map[string]string{}
		}
		for {
			token, err = randomToken()
			if err != nil {
				return err
			}
			_, ok := d.Sessions[token]
			if !ok {
				break
			}
		}
		d.Sessions[token] = phone
		return nil
	})
	if err != nil {
		return "", false, err
	}

	// Deactivate the login code
	err = s.DB.Do(func(d *Data) error {
		if d == nil {
			d = &Data{}
		}
		if d.Users == nil {
			return nil
		}
		if d.Users[phone] == nil {
			d.Users[phone] = &User{}
		}
		if d.Users[phone].LoginCodes == nil {
			return nil
		}
		delete(d.Users[phone].LoginCodes, code)
		return nil
	})
	if err != nil {
		s.logError(err)
	}

	// Return the session token
	return token, true, nil
}

func (s *Server[DataType, UserDataType, StateType]) logError(err error) {
	fmt.Println("ERROR:", err)
}
