//go:build !gogit

package git

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"os/exec"
	"strings"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
)

type Repository interface {
	Branches() ([]*plumbing.Reference, error)
	//	Auth(username string, password string) error
	FetchContext(ctx context.Context, opts *git.FetchOptions) error
	PushContext(ctx context.Context, opts *git.PushOptions) error
	Head() (*plumbing.Reference, error)
	Worktree() (Worktree, error)
}

type Worktree interface {
	Checkout(*git.CheckoutOptions) error
	PullContext(ctx context.Context, opts *git.PullOptions) error
	Status() (git.Status, error)
	AddWithOptions(opts *git.AddOptions) error
	Commit(msg string, opts *git.CommitOptions) (plumbing.Hash, error)
	Reset(opts *git.ResetOptions) error
}

type repository struct {
	gocmd string
	path  string
	// authUsername string
	// authPassword string
}

func PlainOpenWithOptions(path string, opts *git.PlainOpenOptions) (Repository, error) {
	gopath, err := exec.LookPath("git")
	if err != nil {
		return nil, err
	}
	return &repository{path: path, gocmd: gopath}, nil
}

/*
func (r *repository) Auth(username string, password string) error {
	r.authUsername = username
	r.authPassword = password
	return nil
}
*/

func (r *repository) Branches() ([]*plumbing.Reference, error) {
	var branches []*plumbing.Reference
	cmd := exec.Command(r.gocmd, "show-ref", "--branches")
	buf, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("output %s error %w", buf, err)
	}
	br := bytes.NewBuffer(buf)
	for {
		line, err := br.ReadString('\n')
		if err != nil {
			if err == io.EOF && line == "" {
				break
			} else if err != io.EOF && line == "" {
				return nil, err
			}
		}
		fields := strings.Fields(line)
		if len(fields) != 2 {
			return nil, fmt.Errorf("invalid fields %s", line)
		}
		branches = append(branches, plumbing.NewReferenceFromStrings(fields[1], fields[0]))
	}
	return branches, nil
}

func (r *repository) FetchContext(ctx context.Context, opts *git.FetchOptions) error {
	args := []string{"fetch"}
	if opts.Force {
		args = append(args, "-f")
	}
	cmd := exec.CommandContext(ctx, r.gocmd, args...)
	buf, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("output %s error %w", buf, err)
	}

	return nil
}

func (r *repository) PushContext(ctx context.Context, opts *git.PushOptions) error {
	args := []string{"push"}

	if opts.Force {
		args = append(args, "-f")
	}

	/* TODO
	var refs []string
	for _, ref := range opts.RefSpecs {
		refs = append(refs, ref.String())
	}

	args = append(args, strings.Join(refs, " "))
	*/
	cmd := exec.CommandContext(ctx, r.gocmd, args...)
	buf, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("output %s error %w", buf, err)
	}

	return nil
}

func (r *repository) Head() (*plumbing.Reference, error) {
	var head *plumbing.Reference
	cmd := exec.Command(r.gocmd, "symbolic-ref", "--short", "HEAD")
	buf, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("output %s error %w", buf, err)
	}
	br := bytes.NewBuffer(buf)
	for {
		line, err := br.ReadString('\n')
		if err != nil {
			if err == io.EOF && line == "" {
				break
			} else if err != io.EOF && line == "" {
				return nil, err
			}
		}
		fields := strings.Fields(line)
		if len(fields) != 2 {
			return nil, fmt.Errorf("invalid fields %s", line)
		}
		head = plumbing.NewReferenceFromStrings("HEAD", fields[0])
	}
	return head, nil
}

type worktree struct {
	gocmd string
}

func (r *repository) Worktree() (Worktree, error) {
	return &worktree{gocmd: r.gocmd}, nil
}

func (w *worktree) Checkout(opts *git.CheckoutOptions) error {
	args := []string{"checkout"}
	if opts.Create {
		args = append(args, "-b", opts.Branch.Short())
	}
	if opts.Force {
		args = append(args, "-f")
	}
	if opts.Hash.IsZero() {
		args = append(args, opts.Branch.Short())
	} else {
		args = append(args, opts.Hash.String())
	}
	cmd := exec.Command(w.gocmd, args...)
	buf, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("output %s error %w", buf, err)
	}

	return nil
}

func (w *worktree) Status() (git.Status, error) {
	return git.Status{}, nil
}

func (w *worktree) Reset(opts *git.ResetOptions) error {
	args := []string{"reset"}
	if opts.Mode == git.HardReset {
		args = append(args, "--hard")
	}

	args = append(args, opts.Commit.String())

	cmd := exec.Command(w.gocmd, args...)
	buf, err := cmd.CombinedOutput()
	if err != nil {
		return err
	}
	_ = buf
	return nil
}

func (w *worktree) Commit(msg string, opts *git.CommitOptions) (plumbing.Hash, error) {
	cmd := exec.Command(w.gocmd, `commit`,
		fmt.Sprintf(`--author="%s <%s>"`, opts.Author.Name, opts.Author.Email),
		"-m", msg,
		fmt.Sprintf(`--date="%s"`, opts.Author.When.Format(`Mon Jan _2 15:04:05 2006 -0700`)),
	)
	buf, err := cmd.CombinedOutput()
	if err != nil {
		return plumbing.ZeroHash, fmt.Errorf("output %s error %w", buf, err)
	}

	var head *plumbing.Reference
	cmd = exec.Command(w.gocmd, "show-ref", "HEAD")
	buf, err = cmd.CombinedOutput()
	if err != nil {
		return plumbing.ZeroHash, err
	}
	br := bytes.NewBuffer(buf)
	for {
		line, err := br.ReadString('\n')
		if err != nil {
			if err == io.EOF && line == "" {
				break
			} else if err != io.EOF && line == "" {
				return plumbing.ZeroHash, err
			}
		}
		fields := strings.Fields(line)
		if len(fields) != 2 {
			return plumbing.ZeroHash, fmt.Errorf("invalid fields %s", line)
		}
		head = plumbing.NewReferenceFromStrings("HEAD", fields[0])
	}

	return head.Hash(), nil
}

func (w *worktree) PullContext(ctx context.Context, opts *git.PullOptions) error {
	args := []string{"pull"}
	if opts.Force {
		args = append(args, "-f")
	}
	if opts.Depth != 0 {
		args = append(args, fmt.Sprintf("--depth=%d", opts.Depth))
	}

	cmd := exec.CommandContext(ctx, w.gocmd, args...)
	buf, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("output %s error %w", buf, err)
	}

	return nil
}

func (w *worktree) AddWithOptions(opts *git.AddOptions) error {
	cmd := exec.Command(w.gocmd, "add", opts.Path)
	buf, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("output %s error %w", buf, err)
	}

	return nil
}
