package repostatus

import (
	"bufio"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"go.wit.com/log"
)

// GitConfig represents the parsed .git/config data
// type GitConfig map[string]map[string]string
// TODO: switch to protobuf

type remote struct {
	url   string
	fetch string
}

type branch struct {
	remote string
	merge  string
}

type GitConfig struct {
	core       map[string]string  // map[origin] = "https:/git.wit.org/gui/gadgets"
	remotes    map[string]*remote // map[origin] = "https:/git.wit.org/gui/gadgets"
	branches   map[string]*branch // map[guimaster] = origin guimaster
	submodules map[string]string
	hashes     map[string]string
	versions   map[string]string
}

// type GoConfig map[string]string

func ListGitDirectories() []string {
	var all []string
	homeDir, err := os.UserHomeDir()
	if err != nil {
		log.Log(WARN, "Error getting home directory:", err)
		return nil
	}

	srcDir := filepath.Join(homeDir, "go/src")

	err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Log(WARN, "Error accessing path:", path, err)
			return nil
		}

		// Check if the current path is a directory and has a .git subdirectory
		if info.IsDir() && IsGitDir(path) {
			all = append(all, path)
			// fmt.Println(path)
		}

		return nil
	})

	if err != nil {
		log.Log(WARN, "Error walking the path:", srcDir, err)
	}

	return all
}

// IsGitDir checks if a .git directory exists inside the given directory
func IsGitDir(dir string) bool {
	gitDir := filepath.Join(dir, ".git")
	info, err := os.Stat(gitDir)
	if os.IsNotExist(err) {
		return false
	}
	return info.IsDir()
}

// readGitConfig reads and parses the .git/config file
func (rs *RepoStatus) readGitConfig() error {
	filename := filepath.Join(rs.realPath.String(), "/.git/config")
	file, err := os.Open(filename)
	if err != nil {
		log.Log(WARN, "readGitConfig() failed for file:", filename)
		filename = filepath.Join(rs.realPath.String(), "../.git/config")
		log.Log(WARN, "readGitConfig() trying up one directory instead", filename)
		file, err = os.Open(filename)
		if err != nil {
			return err
		}
	}
	defer file.Close()

	var currentSection string = ""
	var currentName string = ""

	rs.gitConfig = new(GitConfig)
	rs.gitConfig.core = make(map[string]string)
	rs.gitConfig.remotes = make(map[string]*remote)
	rs.gitConfig.branches = make(map[string]*branch)
	rs.gitConfig.submodules = make(map[string]string)
	rs.gitConfig.versions = make(map[string]string)
	rs.gitConfig.hashes = make(map[string]string)

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())

		// Skip empty lines and comments
		if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") {
			continue
		}

		// Check for section headers
		if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
			line = strings.Trim(line, "[]")
			parts := strings.Split(line, " ")
			currentSection = parts[0]

			if len(parts) == 2 {
				line = strings.Trim(line, "[]")
				currentName = strings.Trim(parts[1], "\"")
			}
			continue
		}

		partsNew := strings.SplitN(line, "=", 2)
		if len(partsNew) != 2 {
			log.Log(WARN, "error on config section:", currentSection, "line:", line)
		}

		key := strings.TrimSpace(partsNew[0])
		key = strings.TrimSuffix(key, "\"")

		value := strings.TrimSpace(partsNew[1])
		value = strings.TrimSuffix(value, "\"")

		switch currentSection {
		case "core":
			rs.gitConfig.core[key] = value
		case "gui":
			// don't really need gui stuff right now
		case "pull":
			// don't store git config pull settings here
			// probably has 'rebase = false'
		case "remote":
			test, ok := rs.gitConfig.remotes[currentName]
			if !ok {
				test = new(remote)
				rs.gitConfig.remotes[currentName] = test
			}
			log.Log(INFO, "switch currentSection", currentSection, currentName)
			switch key {
			case "url":
				if test.url == value {
					continue
				}
				if test.url == "" {
					test.url = value
					continue
				}
				log.Log(REPO, "error url mismatch", test.url, value)
			case "fetch":
				if test.fetch == value {
					continue
				}
				if test.fetch == "" {
					test.fetch = value
					continue
				}
				log.Log(REPO, "error fetch mismatch", test.fetch, value)
			default:
				log.Log(REPO, "unknown remote:", rs.Path(), line)
			}
		case "branch":
			test, ok := rs.gitConfig.branches[currentName]
			if !ok {
				test = new(branch)
				rs.gitConfig.branches[currentName] = test
				rs.processBranch(currentName)
			}
			switch key {
			case "remote":
				rs.gitConfig.branches[currentName].remote = value
			case "merge":
				rs.gitConfig.branches[currentName].merge = value
			default:
				log.Log(REPO, "error unknown remote:", currentSection, currentName, key, value)
				log.Log(REPO, "unknown branch:", rs.Path(), line)
			}
		case "submodule":
			// test, ok := rs.gitConfig.submodules[currentName]
			switch key {
			case "active":
				// probably 'true' or 'false'
			case "url":
				rs.gitConfig.submodules[currentName] = value
			default:
				log.Log(REPOWARN, "unknown submodule line:", rs.Path(), line)
			}
		default:
			log.Log(REPOWARN, "unknown line:", rs.Path(), line)
		}
	}

	if err := scanner.Err(); err != nil {
		return err
	}

	return nil
}

func (rs *RepoStatus) GitURL() string {
	origin, ok := rs.gitConfig.remotes["origin"]
	if ok {
		return origin.url
	}
	for i, s := range rs.gitConfig.remotes {
		log.Log(WARN, "remote:", i, s.url)
	}
	log.Log(WARN, "GitURL() repo has non-standard origin or is not uploaded")
	return ""
}

func (rs *RepoStatus) GitLsFiles() (bool, string) {
	r := rs.Run([]string{"git", "ls-files"})
	output := strings.Join(r.Stdout, "\n")
	if r.Error != nil {
		log.Warn("git ls-files failed err =", r.Error)
		log.Warn("git ls-files failed output =", output)
		return false, output
	}
	return true, output
}

func (rs *RepoStatus) ReadOnly() bool {
	if rs.readOnly.String() == "true" {
		return true
	} else {
		return false
	}
}

func (rs *RepoStatus) SetReadOnly(b bool) {
	if b {
		rs.readOnly.SetText("true")
	} else {
		rs.readOnly.SetText("false")
	}
}

func (rs *RepoStatus) processBranch(branch string) {
	fullpath := rs.realPath.String()
	log.Log(INFO, "    ", branch)
	hash, ok := rs.gitConfig.hashes[branch]
	filename := fullpath + "/.git/refs/heads/" + branch
	log.Log(INFO, "      hash: need to open", filename)

	data, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Log(WARN, "hash: read failed", filename, rs.Path())
		return
	}
	newhash := strings.TrimSpace(string(data))
	log.Log(INFO, "      hash:", newhash)
	rs.gitConfig.hashes[branch] = newhash
	if ok {
		if hash != newhash {
			log.Log(WARN, "hash changed", hash, rs.Path())
		}
	}

	name, _ := rs.gitDescribeByHash(newhash)
	rs.gitConfig.versions[newhash] = name
	log.Log(INFO, "      hash: version", name)
}

func (rs *RepoStatus) BranchExists(branch string) bool {
	hash, ok := rs.gitConfig.hashes[branch]
	if ok {
		log.Log(REPOWARN, rs.Path(), "found branch", branch, hash)
		return true
	}
	for i, t := range rs.Tags.tags {
		base := filepath.Base(t.tag.String())
		if base == branch {
			log.Info("found branch tag:", i, t.tag.String())
			return true
		}
		// log.Info("not tag:", i, t.tag.String())
	}
	log.Log(REPOWARN, rs.Path(), "did not find branch", branch)
	return false
}