package git

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	gitcontract "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/git"
)

// SyncRepositoryWithAuth clones or updates a repository using gateway-provided credentials.
func (p *Provider) SyncRepositoryWithAuth(ctx context.Context, opts gitcontract.SyncAuthOptions) (gitcontract.SyncResult, error) {
	repoID := strings.TrimSpace(opts.RepoID)
	ref := strings.TrimSpace(opts.Ref)
	cloneURL := strings.TrimSpace(opts.CloneURL)

	if ref == "" {
		return gitcontract.SyncResult{}, fmt.Errorf("ref is required")
	}
	if cloneURL == "" {
		return gitcontract.SyncResult{}, fmt.Errorf("clone url is required")
	}
	if opts.Username == "" || opts.Password == "" {
		return gitcontract.SyncResult{}, fmt.Errorf("clone credentials are required")
	}
	if err := ValidateGatewayRepoID(repoID); err != nil {
		return gitcontract.SyncResult{}, err
	}

	workDir, err := GatewayWorkspaceDir(p.cfg, repoID)
	if err != nil {
		return gitcontract.SyncResult{}, err
	}
	if err := EnsureUnderWorkspace(p.cfg.Git.WorkspacePath, workDir); err != nil {
		return gitcontract.SyncResult{}, err
	}

	snapshotID, err := newSnapshotID()
	if err != nil {
		return gitcontract.SyncResult{}, err
	}

	authRunner, ok := p.runner.(*ShellRunner)
	if !ok {
		return gitcontract.SyncResult{}, fmt.Errorf("runner does not support per-request credentials")
	}

	gitDir := filepath.Join(workDir, ".git")
	isFirstSync := false
	var oldSHA string

	if _, err := os.Stat(gitDir); os.IsNotExist(err) {
		isFirstSync = true
		if err := p.cloneWithAuth(ctx, authRunner, cloneURL, workDir, ref, opts.Username, opts.Password); err != nil {
			return gitcontract.SyncResult{}, err
		}
	} else if err != nil {
		return gitcontract.SyncResult{}, fmt.Errorf("stat git dir: %w", err)
	} else {
		oldSHA, err = p.headSHAWithAuth(ctx, authRunner, workDir, opts.Username, opts.Password)
		if err != nil {
			return gitcontract.SyncResult{}, err
		}
		if err := p.fetchAndCheckoutWithAuth(ctx, authRunner, workDir, ref, opts.Username, opts.Password); err != nil {
			return gitcontract.SyncResult{}, err
		}
	}

	commitSHA, err := p.headSHAWithAuth(ctx, authRunner, workDir, opts.Username, opts.Password)
	if err != nil {
		return gitcontract.SyncResult{}, err
	}

	var fileChanges []gitcontract.FileChange
	var commitChanges []gitcontract.CommitChange

	if isFirstSync {
		paths, err := p.listFilesWithAuth(ctx, authRunner, workDir, opts.Username, opts.Password)
		if err != nil {
			return gitcontract.SyncResult{}, err
		}
		fileChanges = ClassifyAllFiles(paths)

		logOut, _, err := authRunner.RunWithCredentials(ctx, workDir, opts.Username, opts.Password, "log", "--reverse", "--format=%H %P")
		if err != nil {
			return gitcontract.SyncResult{}, err
		}
		commitChanges = ParseCommitLog(logOut)
	} else if oldSHA != commitSHA {
		diffOut, _, err := authRunner.RunWithCredentials(ctx, workDir, opts.Username, opts.Password, "diff", "--name-status", oldSHA+".."+commitSHA)
		if err != nil {
			return gitcontract.SyncResult{}, err
		}
		fileChanges, err = ParseNameStatus(diffOut)
		if err != nil {
			return gitcontract.SyncResult{}, err
		}

		logOut, _, err := authRunner.RunWithCredentials(ctx, workDir, opts.Username, opts.Password, "log", "--reverse", "--format=%H %P", oldSHA+".."+commitSHA)
		if err != nil {
			return gitcontract.SyncResult{}, err
		}
		commitChanges = ParseCommitLog(logOut)
	}

	return gitcontract.SyncResult{
		CloneURL: cloneURL,
		Snapshot: gitcontract.RepositorySnapshot{
			RepoID:     repoID,
			SnapshotID: snapshotID,
			CommitSHA:  commitSHA,
			Ref:        ref,
			Status:     gitcontract.SnapshotStatusReady,
		},
		FileChanges:   fileChanges,
		CommitChanges: commitChanges,
		IsFirstSync:   isFirstSync,
	}, nil
}

func (p *Provider) cloneWithAuth(ctx context.Context, runner *ShellRunner, cloneURL, workDir, ref, username, password string) error {
	parent := filepath.Dir(workDir)
	if err := os.MkdirAll(parent, 0o755); err != nil {
		return fmt.Errorf("create workspace parent: %w", err)
	}

	args := []string{"clone", cloneURL, workDir}
	if !looksLikeSHA(ref) {
		args = append(args, "--branch", ref)
	}

	if _, _, err := runner.RunWithCredentials(ctx, "", username, password, args...); err != nil {
		if !looksLikeSHA(ref) && isRemoteBranchNotFound(err) {
			fallbackArgs := []string{"clone", cloneURL, workDir}
			if _, _, err2 := runner.RunWithCredentials(ctx, "", username, password, fallbackArgs...); err2 != nil {
				return err
			}
			return nil
		}
		return err
	}

	if looksLikeSHA(ref) {
		if _, _, err := runner.RunWithCredentials(ctx, workDir, username, password, "checkout", ref); err != nil {
			return err
		}
	}
	return nil
}

func (p *Provider) fetchAndCheckoutWithAuth(ctx context.Context, runner *ShellRunner, workDir, ref, username, password string) error {
	if _, _, err := runner.RunWithCredentials(ctx, workDir, username, password, "fetch", "origin"); err != nil {
		return err
	}
	checkoutRef := ref
	if !looksLikeSHA(ref) {
		checkoutRef = "origin/" + ref
	}
	_, _, err := runner.RunWithCredentials(ctx, workDir, username, password, "checkout", checkoutRef)
	return err
}

func (p *Provider) headSHAWithAuth(ctx context.Context, runner *ShellRunner, workDir, username, password string) (string, error) {
	out, _, err := runner.RunWithCredentials(ctx, workDir, username, password, "rev-parse", "HEAD")
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(out), nil
}

func (p *Provider) listFilesWithAuth(ctx context.Context, runner *ShellRunner, workDir, username, password string) ([]string, error) {
	out, _, err := runner.RunWithCredentials(ctx, workDir, username, password, "ls-files")
	if err != nil {
		return nil, err
	}
	if strings.TrimSpace(out) == "" {
		return nil, nil
	}
	return strings.Split(strings.TrimSpace(out), "\n"), nil
}

func isRemoteBranchNotFound(err error) bool {
	if err == nil {
		return false
	}
	msg := err.Error()
	return strings.Contains(msg, "Remote branch") && strings.Contains(msg, "not found")
}
