package git

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

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

// Provider implements GitProvider using a shell git runner.
type Provider struct {
	cfg    config.Config
	runner Runner
	logger *slog.Logger
}

// NewProvider creates a Bitbucket Git provider.
func NewProvider(cfg config.Config, runner Runner, logger *slog.Logger) *Provider {
	return &Provider{cfg: cfg, runner: runner, logger: logger}
}

var _ gitcontract.GitProvider = (*Provider)(nil)

// SyncRepository clones or updates a repository and returns sync results.
func (p *Provider) SyncRepository(ctx context.Context, repoID, ref string) (gitcontract.SyncResult, error) {
	repoID = strings.TrimSpace(repoID)
	ref = strings.TrimSpace(ref)
	if ref == "" {
		return gitcontract.SyncResult{}, fmt.Errorf("ref is required")
	}

	if err := ValidateRepoID(repoID); err != nil {
		return gitcontract.SyncResult{}, err
	}

	cloneURL, err := CloneURL(p.cfg.Bitbucket.BaseURL, repoID)
	if err != nil {
		return gitcontract.SyncResult{}, err
	}

	workDir, err := WorkspaceDir(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
	}

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

	if _, err := os.Stat(gitDir); os.IsNotExist(err) {
		isFirstSync = true
		if err := p.clone(ctx, cloneURL, workDir, ref); err != nil {
			return gitcontract.SyncResult{}, err
		}
	} else if err != nil {
		return gitcontract.SyncResult{}, fmt.Errorf("stat git dir: %w", err)
	} else {
		oldSHA, err = p.headSHA(ctx, workDir)
		if err != nil {
			return gitcontract.SyncResult{}, err
		}
		if err := p.fetchAndCheckout(ctx, workDir, ref); err != nil {
			return gitcontract.SyncResult{}, err
		}
	}

	commitSHA, err := p.headSHA(ctx, workDir)
	if err != nil {
		return gitcontract.SyncResult{}, err
	}

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

	if isFirstSync {
		paths, err := p.listFiles(ctx, workDir)
		if err != nil {
			return gitcontract.SyncResult{}, err
		}
		fileChanges = ClassifyAllFiles(paths)

		logOut, err := p.runGit(ctx, workDir, "log", "--reverse", CommitLogFormat)
		if err != nil {
			return gitcontract.SyncResult{}, err
		}
		commitChanges = ParseCommitLog(logOut)
	} else if oldSHA != commitSHA {
		diffOut, err := p.runGit(ctx, workDir, "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 := p.runGit(ctx, workDir, "log", "--reverse", CommitLogFormat, 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) clone(ctx context.Context, cloneURL, workDir, ref 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 := p.runGit(ctx, "", args...); err != nil {
		return err
	}

	if looksLikeSHA(ref) {
		if _, err := p.runGit(ctx, workDir, "checkout", ref); err != nil {
			return err
		}
	}
	return nil
}

func (p *Provider) fetchAndCheckout(ctx context.Context, workDir, ref string) error {
	if _, err := p.runGit(ctx, workDir, "fetch", "origin"); err != nil {
		return err
	}

	checkoutRef := ref
	if !looksLikeSHA(ref) {
		checkoutRef = "origin/" + ref
	}
	if _, err := p.runGit(ctx, workDir, "checkout", checkoutRef); err != nil {
		return err
	}
	return nil
}

func (p *Provider) headSHA(ctx context.Context, workDir string) (string, error) {
	out, err := p.runGit(ctx, workDir, "rev-parse", "HEAD")
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(out), nil
}

func (p *Provider) listFiles(ctx context.Context, workDir string) ([]string, error) {
	out, err := p.runGit(ctx, workDir, "ls-files")
	if err != nil {
		return nil, err
	}
	if strings.TrimSpace(out) == "" {
		return nil, nil
	}
	return strings.Split(strings.TrimSpace(out), "\n"), nil
}

func (p *Provider) runGit(ctx context.Context, dir string, args ...string) (string, error) {
	stdout, _, err := p.runner.Run(ctx, dir, args...)
	return stdout, err
}

func looksLikeSHA(ref string) bool {
	if len(ref) < 7 || len(ref) > 40 {
		return false
	}
	for _, c := range ref {
		if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') {
			return false
		}
	}
	return true
}
