package main

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"strings"
	"sync"

	"github.com/stephenafamo/bob"
	"gitlab.artefactual.com/dcosme/am-scripts/assets"
	"gitlab.artefactual.com/dcosme/am-scripts/database/mcp/models"
)

func server() error {
	fileServer := http.FileServer(http.FS(assets.Assets))
	http.Handle("/assets/", http.StripPrefix("/assets/", fileServer))
	state := &State{
		Theme: ThemeLight,
		Mysql: &Service{
			Name:   "Mysql",
			Status: "Disconnected",
		},
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		Home(state).Render(w)
	})
	http.HandleFunc("/upload", state.uploadInput)
	http.HandleFunc("/mysql", state.connectDB)
	http.HandleFunc("/report", state.report)

	slog.Info("listening", "addr", "4001")
	err := http.ListenAndServe(":4001", http.DefaultServeMux)
	if err != nil {
		return err
	}
	return nil
}

func (state *State) report(w http.ResponseWriter, r *http.Request) {
	mu := sync.Mutex{}
	mu.Lock()
	defer mu.Unlock()

	err := func() error {
		if len(state.Input) == 0 && state.Report == nil {
			return errors.New("need input before creating report")
		}
		if state.Mysql.Status != "Connected" {
			return errors.New("need database online")
		}

		slog.Info("creating report")
		for idx, record := range state.Input {
			q := models.LocationsPackages.Query()
			like := fmt.Sprintf("%%%s%%", record)
			q.Apply(models.SelectWhere.LocationsPackages.CurrentPath.Like(like))
			res, err := q.All(context.Background(), state.db)
			assertNoErr(err)

			row := []string{record}
			for _, r := range res {
				slog.Info("AIP Found: " + r.CurrentPath)
				row = append(row, r.CurrentPath)
			}
			state.Report[idx] = row
		}

		return Report(state).Render(w)
	}()
	if err != nil {
		state.Err = err
		slog.Error(err.Error())
		Report(state).Render(w)
	}
}

func (state *State) connectDB(w http.ResponseWriter, r *http.Request) {
	err := func() error {
		state.Mysql.Err = nil
		err := r.ParseForm()
		if err != nil {
			return err
		}
		connString := r.PostFormValue("mysql")
		slog.Info("connecting mysql", "conn", connString)
		db, err := openDB(connString)
		if err != nil {
			return fmt.Errorf("%s: %w", connString, err)
		}
		state.db = bob.NewDB(db)
		slog.Info("connected to database")
		state.Mysql.Status = "Connected"
		state.Mysql.URL = connString

		return Home(state).Render(w)
	}()
	if err != nil {
		state.Mysql.Err = err
		slog.Error(err.Error())
		Home(state).Render(w)
	}
}

func (state *State) uploadInput(w http.ResponseWriter, r *http.Request) {
	err := func() error {
		type InputFile struct {
			Name     string `json:"name"`
			Contents string `json:"contents"`
			Mime     string `json:"mime"`
		}
		type UploadPayload struct {
			InputFile []InputFile `json:"input-file"`
		}
		var payload UploadPayload
		err := bind(&payload, r)
		if err != nil {
			return err
		}
		if len(payload.InputFile) != 1 {
			return fmt.Errorf("exactly one file is allowed to be uploaded, got: %d", len(payload.InputFile))
		}
		file := payload.InputFile[0]

		raw := file.Contents
		content, err := base64.StdEncoding.DecodeString(raw)
		if err != nil {
			return err
		}
		lines := strings.Split(strings.TrimSpace(string(content)), "\n")
		slog.Info("file uploaded", "name", file.Name)
		state.Input = lines
		state.Report = make([][]string, len(lines))

		return Home(state).Render(w)
	}()

	if err != nil {
		slog.Error(err.Error())
		w.WriteHeader(http.StatusInternalServerError)
	}
}

func bind(v any, r *http.Request) error {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		return err
	}
	json.Unmarshal(body, v)
	return nil
}
