// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT

package v2

import (
	"net/http"

	repo_model "code.gitea.io/gitea/models/repo"
	apierrors "code.gitea.io/gitea/modules/errors"
	"code.gitea.io/gitea/modules/gitrepo"
	"code.gitea.io/gitea/modules/json"
	"code.gitea.io/gitea/services/context"
)

// BatchFileRequest represents a request to get multiple files
type BatchFileRequest struct {
	Owner  string   `json:"owner" binding:"Required"`
	Repo   string   `json:"repo" binding:"Required"`
	Ref    string   `json:"ref"`
	Paths  []string `json:"paths" binding:"Required"`
	Format string   `json:"format"` // "content" or "metadata"
}

// BatchFileResult represents the result for a single file in batch
type BatchFileResult struct {
	Path     string `json:"path"`
	Content  string `json:"content,omitempty"`
	Encoding string `json:"encoding,omitempty"`
	SHA      string `json:"sha,omitempty"`
	Size     int64  `json:"size,omitempty"`
	Type     string `json:"type,omitempty"`
	Error    string `json:"error,omitempty"`
}

// BatchFileResponse represents the response for batch file retrieval
type BatchFileResponse struct {
	Owner   string            `json:"owner"`
	Repo    string            `json:"repo"`
	Ref     string            `json:"ref"`
	Results []BatchFileResult `json:"results"`
}

// BatchGetFiles retrieves multiple files in a single request
// This is optimized for AI tools that need to fetch multiple files at once
func BatchGetFiles(ctx *context.APIContext) {
	var req BatchFileRequest
	if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
		ctx.APIErrorWithCode(apierrors.ValidationFailed, map[string]any{
			"field": "body",
			"error": err.Error(),
		})
		return
	}

	// Validate path count (limit to prevent abuse)
	if len(req.Paths) > 100 {
		ctx.APIErrorWithCode(apierrors.ValidationFailed, map[string]any{
			"field":   "paths",
			"message": "maximum 100 paths per request",
			"count":   len(req.Paths),
		})
		return
	}

	if len(req.Paths) == 0 {
		ctx.APIErrorWithCode(apierrors.ValidationFailed, map[string]any{
			"field":   "paths",
			"message": "at least one path is required",
		})
		return
	}

	// Get repository
	repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, req.Owner, req.Repo)
	if err != nil {
		if repo_model.IsErrRepoNotExist(err) {
			ctx.APIErrorWithCode(apierrors.RepoNotFound, map[string]any{
				"owner": req.Owner,
				"repo":  req.Repo,
			})
			return
		}
		ctx.APIErrorWithCode(apierrors.InternalError, map[string]any{
			"error": err.Error(),
		})
		return
	}

	// Check access (basic check - user must be signed in or repo is public)
	if repo.IsPrivate && !ctx.IsSigned {
		ctx.APIErrorWithCode(apierrors.PermAccessDenied)
		return
	}

	// Open git repo
	gitRepo, err := gitrepo.OpenRepository(ctx, repo)
	if err != nil {
		ctx.APIErrorWithCode(apierrors.InternalError, map[string]any{
			"error": "failed to open repository",
		})
		return
	}
	defer gitRepo.Close()

	// Determine ref
	ref := req.Ref
	if ref == "" {
		ref = repo.DefaultBranch
	}

	// Get commit for ref
	commit, err := gitRepo.GetCommit(ref)
	if err != nil {
		// Try as branch
		commit, err = gitRepo.GetBranchCommit(ref)
		if err != nil {
			// Try as tag
			commit, err = gitRepo.GetTagCommit(ref)
			if err != nil {
				ctx.APIErrorWithCode(apierrors.RefNotFound, map[string]any{
					"ref": ref,
				})
				return
			}
		}
	}

	// Fetch each file
	results := make([]BatchFileResult, 0, len(req.Paths))
	metadataOnly := req.Format == "metadata"

	for _, path := range req.Paths {
		result := BatchFileResult{Path: path}

		entry, err := commit.GetTreeEntryByPath(path)
		if err != nil {
			result.Error = "file not found"
			results = append(results, result)
			continue
		}

		result.SHA = entry.ID.String()
		result.Size = entry.Size()
		result.Type = entry.Mode().String()

		if !metadataOnly && !entry.IsDir() {
			// Get file content (limit size to prevent memory issues)
			if entry.Size() > 10*1024*1024 { // 10MB limit
				result.Error = "file too large (>10MB)"
			} else {
				blob := entry.Blob()
				reader, err := blob.DataAsync()
				if err != nil {
					result.Error = "failed to read file content"
				} else {
					defer reader.Close()
					content := make([]byte, entry.Size())
					_, err = reader.Read(content)
					if err != nil && err.Error() != "EOF" {
						result.Error = "failed to read file content"
					} else {
						result.Content = string(content)
						result.Encoding = "utf-8"
					}
				}
			}
		}

		results = append(results, result)
	}

	ctx.JSON(http.StatusOK, BatchFileResponse{
		Owner:   req.Owner,
		Repo:    req.Repo,
		Ref:     ref,
		Results: results,
	})
}

// BatchRepoRequest represents a request to get info about multiple repos
type BatchRepoRequest struct {
	Repos []struct {
		Owner string `json:"owner"`
		Repo  string `json:"repo"`
	} `json:"repos" binding:"Required"`
	Fields []string `json:"fields"` // Which fields to include
}

// BatchRepoResult represents the result for a single repo in batch
type BatchRepoResult struct {
	Owner       string `json:"owner"`
	Repo        string `json:"repo"`
	FullName    string `json:"full_name,omitempty"`
	Description string `json:"description,omitempty"`
	Private     bool   `json:"private,omitempty"`
	Fork        bool   `json:"fork,omitempty"`
	Stars       int    `json:"stars,omitempty"`
	Forks       int    `json:"forks,omitempty"`
	Language    string `json:"language,omitempty"`
	Error       string `json:"error,omitempty"`
}

// BatchGetRepos retrieves information about multiple repositories
func BatchGetRepos(ctx *context.APIContext) {
	var req BatchRepoRequest
	if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil {
		ctx.APIErrorWithCode(apierrors.ValidationFailed, map[string]any{
			"field": "body",
			"error": err.Error(),
		})
		return
	}

	// Validate repo count
	if len(req.Repos) > 50 {
		ctx.APIErrorWithCode(apierrors.ValidationFailed, map[string]any{
			"field":   "repos",
			"message": "maximum 50 repositories per request",
			"count":   len(req.Repos),
		})
		return
	}

	if len(req.Repos) == 0 {
		ctx.APIErrorWithCode(apierrors.ValidationFailed, map[string]any{
			"field":   "repos",
			"message": "at least one repository is required",
		})
		return
	}

	results := make([]BatchRepoResult, 0, len(req.Repos))

	for _, repoRef := range req.Repos {
		result := BatchRepoResult{
			Owner: repoRef.Owner,
			Repo:  repoRef.Repo,
		}

		repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, repoRef.Owner, repoRef.Repo)
		if err != nil {
			if repo_model.IsErrRepoNotExist(err) {
				result.Error = "repository not found"
			} else {
				result.Error = "failed to fetch repository"
			}
			results = append(results, result)
			continue
		}

		// Check access
		if repo.IsPrivate && !ctx.IsSigned {
			result.Error = "access denied"
			results = append(results, result)
			continue
		}

		result.FullName = repo.FullName()
		result.Description = repo.Description
		result.Private = repo.IsPrivate
		result.Fork = repo.IsFork
		result.Stars = repo.NumStars
		result.Forks = repo.NumForks
		result.Language = repo.PrimaryLanguage.Language

		results = append(results, result)
	}

	ctx.JSON(http.StatusOK, map[string]any{
		"results": results,
	})
}
