package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"io"
	"bufio"
	"strings"
	"flag"
	"net/http" 
	"net/url"
	"encoding/json"
	"golang.org/x/net/html"
	"plastikfrucht.com/go/stastaca/htmlutil"
)

const USAGE_STR = "usage:	stastaca -if input.html [-of output.html] -user username"
const USER_STATUS_BASE_URL = "http://status.cafe/users"
const USER_STATUS_JSON_NAME = "status.json"

type UserStatus struct {
	Author	string
	Content string
	Face string
	TimeAgo string
}

func main() {
	var username, inFname, outFname string
	flag.StringVar(&username, "user", "", "the status-cafe username whose status we want to fetch")
	flag.StringVar(&inFname, "if", "", "the input html-file")
	flag.StringVar(&outFname, "of", "", "the output html-file")
	flag.Parse()

	if username == "" {
		fmt.Fprintln(os.Stderr, "Missing '-user' flag")
		fmt.Fprintln(os.Stderr, USAGE_STR)
		os.Exit(1)
	} else if inFname == "" {
		fmt.Fprintln(os.Stderr, "Missing '-if' flag")
		fmt.Fprintln(os.Stderr, USAGE_STR)
		os.Exit(1)
	}

	status, err := fetchStatusDummy(username) // get status from the INTERNETS!!!!!
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to fetch status for user %q:\n%v\nAborted.\n", username, err)
		os.Exit(1)
	}

	var outSB strings.Builder
	if err := renderUpdatedHtml(&outSB, inFname, status); err != nil { // parse and update LO(CA)L FILEZ!!!
		fmt.Fprintln(os.Stderr, "Failed to parse/render html:", err)
		os.Exit(1)
	}
	renderedHtml := outSB.String()

	var of *os.File = nil

	if outFname == "" {
		of = os.Stdout
	} else {
		of, err = os.Create(outFname)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Failed to create output file %q:\n%v\nAborted.\n", outFname, err)
			os.Exit(1)
		}
		defer func() { closeFileOrPanic(of) }()
	} 

	w := bufio.NewWriter(of)
	_, err = w.WriteString(renderedHtml)
	if err != nil {
		fmt.Fprintln(os.Stderr, "Failed to write output file:", err)
		os.Exit(1)
	}
	w.Flush()
}


func fetchStatusDummy(username string) (UserStatus, error) {
	status := UserStatus{Content: "hello world & goodbye!", Face: "<3", Author: username, TimeAgo: "8 days ago"}
	return status, nil

}

func fetchStatus(username string) (UserStatus, error) {
	var status UserStatus

	statusURL := strings.Join([]string{USER_STATUS_BASE_URL, url.QueryEscape(username), USER_STATUS_JSON_NAME}, "/")
	resp, err := http.Get(statusURL)
	if err != nil {
		log.Println("Failed to get URL", err)
		return status, err
	}

	defer func() {
		if err := resp.Body.Close(); err != nil {
			log.Panicln("Failed to close respone body", err)
		}
	}()

	jsonBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		log.Println("Failed to read response", err)
		return status, err
	}

	err = json.Unmarshal(jsonBytes, &status)
	if err != nil {
		log.Println("Could not parse json", err)
		return status, err
	}

	return status, nil
}


func renderUpdatedHtml(out io.Writer, fname string, status UserStatus) error {
	htmlFile, err := os.Open(fname)
	if err != nil {
		log.Println("Could not open html file:", err)
		return err
	}

	htmlFileReader := bufio.NewReader(htmlFile)
	htmlRootNode, err := html.Parse(htmlFileReader)
	if err != nil {
		log.Println("Failed parsing html", err)
		closeFileOrPanic(htmlFile) // We wanna close htmlFile early, so no defer.
		return err
	}
	defer closeFileOrPanic(htmlFile) // We wanna close htmlFile early, so no defer.

	cafeNode := htmlutil.FindElementWithID(htmlRootNode, "statuscafe")
	if cafeNode == nil {
		log.Println("'statuscafe' div not found")
		return errors.New("No div element with id 'statuscafe' found")
	}

	/*
	var username, userURL string
	htmlutil.FilterNode(cafeNode, func (n *html.Node) bool {
		if n.Type == html.ElementNode && n.Data == "a" {
			for _, attr := range n.Attr {
				if attr.Key == "href" {
					_, name, foundUserLink := strings.Cut(attr.Val, "status.cafe/users/")
					if foundUserLink && name != "" {
						username = name
						userURL = attr.Val
						return true
					}
				} 
			}
		}
		return false
	})
	if username == "" || userURL == "" {
		return errors.New("No anchor element with href='status.cafe/users/...' found inside 'statuscafe' div (or username empty)")
	}
	*/

	usernameNode := htmlutil.FindElementWithID(cafeNode, "statuscafe-username")
	contentNode := htmlutil.FindElementWithID(cafeNode, "statuscafe-content")
	if usernameNode == nil {
		return errors.New("No div with id='statuscafe-username' inside statuscafe div")
	}
	if contentNode == nil {
		return errors.New("No div with id='statuscafe-content' inside statuscafe div")
	}
	
	htmlutil.RemoveAllChildren(usernameNode)
	htmlutil.RemoveAllChildren(contentNode)

	userURL := strings.Join([]string{USER_STATUS_BASE_URL, url.QueryEscape(status.Author)}, "/")
	usernameChildHtml := fmt.Sprintf("<a href=%q>%s</a> %s", userURL, status.Author, status.Face)
	if err := htmlutil.AppendHtmlAsChilds(usernameNode, usernameChildHtml); err != nil {
		log.Println("Failed to append child-html to statuscafe-username")
		return err
	}
	contentChildHtml := status.Content
	if err := htmlutil.AppendHtmlAsChilds(contentNode, contentChildHtml); err != nil {
		log.Println("Failed to append child-html to statuscafe-content")
		return err
	}

	if err := html.Render(out, htmlRootNode); err != nil {
		log.Println("Failed to render html", err)
		return err
	}

	return nil
}


func closeFileOrPanic(f *os.File) {
	// closing files can fail! Make sure it does not fail silently
	// cf. https://gobyexample.com/defer
	err := f.Close()
	if err != nil {
		log.Panicln("Failed closing file:", err)
	}
}
