package main

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"errors"
	"html/template"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"
)

type User struct {
	Email    string
	Password string
}

type Purchase struct {
	Email     string
	Plan      string
	ConfigRaw string
	CreatedAt time.Time
}

type Server struct {
	mu        sync.RWMutex
	users     map[string]User
	sessions  map[string]string
	purchases map[string][]Purchase
	tpls      *template.Template
	apiClient *APIClient
}

type APIClient struct {
	BaseURL string
	BuyPath string
	APIKey  string
	Client  *http.Client
}

type BuyConfigRequest struct {
	Email string `json:"email"`
	Plan  string `json:"plan"`
}

type BuyConfigResponse struct {
	Config string `json:"config"`
}

func main() {
	addr := getenv("APP_ADDR", ":8080")
	baseURL := getenv("API_BASE_URL", "https://example.com")
	buyPath := getenv("API_BUY_PATH", "/v1/configs/buy")
	apiKey := getenv("API_KEY", "")

	tpls, err := template.ParseGlob("templates/*.html")
	if err != nil {
		log.Fatalf("parse templates: %v", err)
	}

	s := &Server{
		users:     map[string]User{},
		sessions:  map[string]string{},
		purchases: map[string][]Purchase{},
		tpls:      tpls,
		apiClient: &APIClient{
			BaseURL: strings.TrimRight(baseURL, "/"),
			BuyPath: "/" + strings.TrimLeft(buyPath, "/"),
			APIKey:  apiKey,
			Client: &http.Client{
				Timeout: 15 * time.Second,
			},
		},
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/", s.homeHandler)
	mux.HandleFunc("/register", s.registerHandler)
	mux.HandleFunc("/login", s.loginHandler)
	mux.HandleFunc("/logout", s.logoutHandler)
	mux.HandleFunc("/cabinet", s.cabinetHandler)
	mux.HandleFunc("/buy", s.buyHandler)
	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

	log.Printf("server started at %s", addr)
	if err := http.ListenAndServe(addr, mux); err != nil {
		log.Fatal(err)
	}
}

func (s *Server) homeHandler(w http.ResponseWriter, r *http.Request) {
	email, _ := s.currentUser(r)
	_ = s.render(w, "home.html", map[string]any{
		"Title": "RemnaWave VPN",
		"Email": email,
	})
}

func (s *Server) registerHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		_ = s.render(w, "register.html", map[string]any{"Title": "Registration"})
		return
	}
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	email := strings.TrimSpace(r.FormValue("email"))
	password := strings.TrimSpace(r.FormValue("password"))
	if email == "" || password == "" {
		_ = s.render(w, "register.html", map[string]any{
			"Title": "Registration", "Error": "Fill all fields.",
		})
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	if _, ok := s.users[email]; ok {
		_ = s.render(w, "register.html", map[string]any{
			"Title": "Registration", "Error": "User already exists.",
		})
		return
	}

	s.users[email] = User{Email: email, Password: password}
	http.Redirect(w, r, "/login", http.StatusSeeOther)
}

func (s *Server) loginHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		_ = s.render(w, "login.html", map[string]any{"Title": "Authorization"})
		return
	}
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	email := strings.TrimSpace(r.FormValue("email"))
	password := strings.TrimSpace(r.FormValue("password"))

	s.mu.RLock()
	user, ok := s.users[email]
	s.mu.RUnlock()
	if !ok || user.Password != password {
		_ = s.render(w, "login.html", map[string]any{
			"Title": "Authorization", "Error": "Invalid credentials.",
		})
		return
	}

	token, err := newToken()
	if err != nil {
		http.Error(w, "token error", http.StatusInternalServerError)
		return
	}

	s.mu.Lock()
	s.sessions[token] = email
	s.mu.Unlock()

	http.SetCookie(w, &http.Cookie{
		Name:     "session_token",
		Value:    token,
		Path:     "/",
		HttpOnly: true,
		SameSite: http.SameSiteLaxMode,
		MaxAge:   86400 * 7,
	})
	http.Redirect(w, r, "/cabinet", http.StatusSeeOther)
}

func (s *Server) logoutHandler(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("session_token")
	if err == nil {
		s.mu.Lock()
		delete(s.sessions, cookie.Value)
		s.mu.Unlock()
	}

	http.SetCookie(w, &http.Cookie{
		Name:     "session_token",
		Value:    "",
		Path:     "/",
		HttpOnly: true,
		MaxAge:   -1,
	})
	http.Redirect(w, r, "/", http.StatusSeeOther)
}

func (s *Server) cabinetHandler(w http.ResponseWriter, r *http.Request) {
	email, ok := s.currentUser(r)
	if !ok {
		http.Redirect(w, r, "/login", http.StatusSeeOther)
		return
	}

	s.mu.RLock()
	userPurchases := s.purchases[email]
	s.mu.RUnlock()

	_ = s.render(w, "cabinet.html", map[string]any{
		"Title":     "Personal cabinet",
		"Email":     email,
		"Purchases": userPurchases,
	})
}

func (s *Server) buyHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}

	email, ok := s.currentUser(r)
	if !ok {
		http.Redirect(w, r, "/login", http.StatusSeeOther)
		return
	}

	plan := strings.TrimSpace(r.FormValue("plan"))
	if plan == "" {
		http.Redirect(w, r, "/cabinet", http.StatusSeeOther)
		return
	}

	config, err := s.apiClient.BuyConfig(email, plan)
	if err != nil {
		s.mu.RLock()
		userPurchases := s.purchases[email]
		s.mu.RUnlock()

		_ = s.render(w, "cabinet.html", map[string]any{
			"Title":     "Personal cabinet",
			"Email":     email,
			"Purchases": userPurchases,
			"Error":     "API error: " + err.Error(),
		})
		return
	}

	p := Purchase{
		Email:     email,
		Plan:      plan,
		ConfigRaw: config,
		CreatedAt: time.Now(),
	}

	s.mu.Lock()
	s.purchases[email] = append(s.purchases[email], p)
	s.mu.Unlock()

	http.Redirect(w, r, "/cabinet", http.StatusSeeOther)
}

func (s *Server) currentUser(r *http.Request) (string, bool) {
	cookie, err := r.Cookie("session_token")
	if err != nil {
		return "", false
	}

	s.mu.RLock()
	email, ok := s.sessions[cookie.Value]
	s.mu.RUnlock()
	return email, ok
}

func (s *Server) render(w http.ResponseWriter, page string, data any) error {
	if err := s.tpls.ExecuteTemplate(w, page, data); err != nil {
		http.Error(w, "template error", http.StatusInternalServerError)
		return err
	}
	return nil
}

func (a *APIClient) BuyConfig(email, plan string) (string, error) {
	if a.APIKey == "" {
		return "", errors.New("API_KEY is empty")
	}

	reqBody, err := json.Marshal(BuyConfigRequest{
		Email: email,
		Plan:  plan,
	})
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest(http.MethodPost, a.BaseURL+a.BuyPath, bytes.NewBuffer(reqBody))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+a.APIKey)

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

	if resp.StatusCode >= 400 {
		return "", errors.New("remote status: " + resp.Status)
	}

	var out BuyConfigResponse
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return "", err
	}
	if out.Config == "" {
		return "", errors.New("empty config in response")
	}
	return out.Config, nil
}

func newToken() (string, error) {
	buf := make([]byte, 32)
	if _, err := rand.Read(buf); err != nil {
		return "", err
	}
	return hex.EncodeToString(buf), nil
}

func getenv(k, fallback string) string {
	v := strings.TrimSpace(os.Getenv(k))
	if v == "" {
		return fallback
	}
	return v
}
