package welcome

import (
	"bytes"
	"embed"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
	"os"

	"github.com/gorilla/mux"

	"github.com/giolekva/pcloud/core/installer"
	"github.com/giolekva/pcloud/core/installer/server"
	"github.com/giolekva/pcloud/core/installer/soft"
)

//go:embed templates/*
var tmpls embed.FS

//go:embed static/*
var staticAssets embed.FS

type templates struct {
	createAccount        *template.Template
	createAccountSuccess *template.Template
}

func parseTemplatesWelcome(fs embed.FS) (templates, error) {
	base, err := template.New("base.html").ParseFS(fs, "templates/base.html")
	if err != nil {
		return templates{}, err
	}
	parse := func(path string) (*template.Template, error) {
		if b, err := base.Clone(); err != nil {
			return nil, err
		} else {
			return b.ParseFS(fs, path)
		}
	}
	createAccount, err := parse("templates/create-account.html")
	if err != nil {
		return templates{}, err
	}
	createAccountSuccess, err := parse("templates/create-account-success.html")
	if err != nil {
		return templates{}, err
	}
	return templates{createAccount, createAccountSuccess}, nil
}

type Server struct {
	port              int
	repo              soft.RepoIO
	nsCreator         installer.NamespaceCreator
	hf                installer.HelmFetcher
	createAccountAddr string
	loginAddr         string
	membershipsAddr   string
	tmpl              templates
}

func NewServer(
	port int,
	repo soft.RepoIO,
	nsCreator installer.NamespaceCreator,
	hf installer.HelmFetcher,
	createAccountAddr string,
	loginAddr string,
	membershipsAddr string,
) (*Server, error) {
	tmplts, err := parseTemplatesWelcome(tmpls)
	if err != nil {
		return nil, err
	}
	return &Server{
		port,
		repo,
		nsCreator,
		hf,
		createAccountAddr,
		loginAddr,
		membershipsAddr,
		tmplts,
	}, nil
}

func (s *Server) Start() {
	r := mux.NewRouter()
	r.PathPrefix("/static/").Handler(server.NewCachingHandler(http.FileServer(http.FS(staticAssets))))
	r.Path("/").Methods("POST").HandlerFunc(s.createAccount)
	r.Path("/").Methods("GET").HandlerFunc(s.createAccountForm)
	http.Handle("/", r)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", s.port), nil))
}

func (s *Server) createAccountForm(w http.ResponseWriter, r *http.Request) {
	s.renderRegistrationForm(w, formData{})
}

type formData struct {
	UsernameErrors []string
	PasswordErrors []string
	Data           createAccountReq
}

type cpFormData struct {
	UsernameErrors []string
	PasswordErrors []string
	Password       string
}

type createAccountReq struct {
	Username    string `json:"username,omitempty"`
	Password    string `json:"password,omitempty"`
	SecretToken string `json:"secretToken,omitempty"`
}

type apiCreateAccountReq struct {
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"`
}

type ValidationError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

type ErrorResponse struct {
	Errors []ValidationError `json:"errors"`
}

func extractReq(r *http.Request) (createAccountReq, error) {
	var req createAccountReq
	if err := func() error {
		var err error
		if err = r.ParseForm(); err != nil {
			return err
		}
		if req.Username, err = server.GetFormValue(r.PostForm, "username"); err != nil {
			return err
		}
		if req.Password, err = server.GetFormValue(r.PostForm, "password"); err != nil {
			return err
		}
		if req.SecretToken, err = server.GetFormValue(r.PostForm, "secret-token"); err != nil {
			return err
		}
		return nil
	}(); err != nil {
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			return createAccountReq{}, err
		}
	}
	return req, nil
}

func (s *Server) renderRegistrationForm(w http.ResponseWriter, data formData) {
	if err := s.tmpl.createAccount.Execute(w, data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func (s *Server) renderRegistrationSuccess(w http.ResponseWriter, loginAddr string) {
	data := struct {
		LoginAddr string
	}{
		LoginAddr: loginAddr,
	}
	if err := s.tmpl.createAccountSuccess.Execute(w, data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

type identityCreateResp struct {
	Id string `json:"id"`
}

func (s *Server) createAccount(w http.ResponseWriter, r *http.Request) {
	req, err := extractReq(r)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	var idResp identityCreateResp
	{
		var buf bytes.Buffer
		cr := apiCreateAccountReq{req.Username, req.Password}
		if err := json.NewEncoder(&buf).Encode(cr); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		resp, err := http.Post(s.createAccountAddr, "application/json", &buf)
		if err != nil {
			var respBody bytes.Buffer
			if _, err := io.Copy(&respBody, resp.Body); err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
			}
			respStr := respBody.String()
			log.Println(respStr)
			http.Error(w, respStr, http.StatusInternalServerError)
			return
		}
		if resp.StatusCode != http.StatusOK {
			var errResponse ErrorResponse
			if err := json.NewDecoder(resp.Body).Decode(&errResponse); err != nil {
				http.Error(w, "Error Decoding JSON", http.StatusInternalServerError)
				return
			}
			var usernameErrors, passwordErrors []string
			for _, err := range errResponse.Errors {
				if err.Field == "username" {
					usernameErrors = append(usernameErrors, err.Message)
				}
				if err.Field == "password" {
					passwordErrors = append(passwordErrors, err.Message)
				}
			}
			s.renderRegistrationForm(w, formData{
				usernameErrors,
				passwordErrors,
				req,
			})
			return
		}
		if err := json.NewDecoder(resp.Body).Decode(&idResp); err != nil {
			http.Error(w, "Error Decoding JSON", http.StatusInternalServerError)
			return
		}
	}
	if err := s.createUser(idResp.Id, req.Username); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	s.renderRegistrationSuccess(w, s.loginAddr)
}

type firstAccount struct {
	Created bool    `json:"created"`
	Domain  string  `json:"domain"`
	Groups  []group `json:"groups"`
}

type user struct {
	Id       string `json:"id"`
	Username string `json:"username"`
	Email    string `json:"email"`
}

type group struct {
	Id          string `json:"id"`
	Title       string `json:"title"`
	Description string `json:"description"`
}

type initRequest struct {
	User   user    `json:"user"`
	Groups []group `json:"groups"`
}

func (s *Server) createUser(id, username string) error {
	_, err := s.repo.Do(func(r soft.RepoFS) (string, error) {
		var fa firstAccount
		if err := soft.ReadYaml(r, "first-account.yaml", &fa); err != nil {
			return "", err
		}
		var resp *http.Response
		var err error
		u := user{
			id,
			username,
			fmt.Sprintf("%s@%s", username, fa.Domain),
		}
		if fa.Created {
			var buf bytes.Buffer
			if err := json.NewEncoder(&buf).Encode(u); err != nil {
				return "", err
			}
			resp, err = http.Post(
				fmt.Sprintf("%s/api/users", s.membershipsAddr),
				"applications/json",
				&buf,
			)
		} else {
			req := initRequest{
				u,
				fa.Groups,
			}
			var buf bytes.Buffer
			if err := json.NewEncoder(&buf).Encode(req); err != nil {
				return "", err
			}
			resp, err = http.Post(
				fmt.Sprintf("%s/api/init", s.membershipsAddr),
				"applications/json",
				&buf,
			)
			fa.Created = true
			if err := soft.WriteYaml(r, "first-account.yaml", fa); err != nil {
				return "", err
			}
		}
		if err != nil {
			return "", err
		}
		defer resp.Body.Close()
		fmt.Printf("Memberships resp: %d", resp.StatusCode)
		io.Copy(os.Stdout, resp.Body)
		if resp.StatusCode != http.StatusOK {
			return "", fmt.Errorf("memberships error")
		}
		return "initialized groups for first account", nil
	})
	return err
}
