package roles

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/contentpayload"
	"github.com/tidbcloud/AgentTeam/internal/service/content/migration"
	rolesstore "github.com/tidbcloud/AgentTeam/internal/store/resource/roles"
)

// ExportExactRolePackage resolves every portable manifest asset from one
// caller-selected final repository commit and verifies both repository identity
// and the hashes stored on the legacy RoleVersion.
func (s AGSHTTPRolePackageContentStore) ExportExactRolePackage(ctx context.Context, workspaceID, roleID, repositoryID, commitSHA string, items []rolesstore.RolePackageManifestItem, limits contentpayload.Limits) (migration.Export, error) {
	if s.Handler == nil && strings.TrimSpace(s.BaseURL) == "" {
		return migration.Export{}, errRolePackageContentRequired()
	}
	var owner, repository string
	files := make([]migration.File, 0, len(items))
	var total int64
	for _, item := range items {
		switch roleManifestItemProjection(item) {
		case roleManifestItemProjectionExternal:
			if strings.TrimSpace(item.ContentRef) != "" || strings.TrimSpace(item.ContentID) != "" || strings.TrimSpace(item.ContentVersionID) != "" || strings.TrimSpace(item.ContentPath) != "" || strings.TrimSpace(item.ContentHash) != "" || strings.TrimSpace(item.Content) != "" {
				return migration.Export{}, invalidRolePackageContract("role content migration external item has local projection")
			}
			continue
		case roleManifestItemProjectionExact:
		default:
			return migration.Export{}, invalidRolePackageContract("role content migration manifest contains unsupported item")
		}
		assetPath, err := rolePackageAssetPath(RolePackageAsset{ItemType: item.ItemType, Name: item.Name})
		if err != nil {
			return migration.Export{}, err
		}
		location, err := exactRolePackageLegacyRef(item.ContentRef, commitSHA)
		if err != nil || location.Path != assetPath {
			return migration.Export{}, invalidRolePackageContract("role content migration source ref does not match manifest")
		}
		if owner == "" {
			owner, repository = location.Owner, location.Repository
			if err := s.requireExactRolePackageRepository(ctx, owner, repository, repositoryID); err != nil {
				return migration.Export{}, err
			}
		} else if owner != location.Owner || repository != location.Repository {
			return migration.Export{}, invalidRolePackageContract("role content migration spans repositories")
		}
		remaining := limits.MaxTotalBytes - total
		maxFileBytes := limits.MaxFileBytes
		if remaining < maxFileBytes {
			maxFileBytes = remaining
		}
		if maxFileBytes <= 0 {
			return migration.Export{}, invalidRolePackageContract("role content migration source exceeds bounds")
		}
		content, err := s.resolveExactRolePackage(ctx, location.Ref, maxFileBytes)
		if err != nil {
			return migration.Export{}, err
		}
		data := []byte(content)
		if strings.TrimSpace(item.ContentHash) == "" || !strings.EqualFold(rolePackageContentHash(content), strings.TrimSpace(item.ContentHash)) {
			return migration.Export{}, rolePackageDependencyError(fmt.Errorf("role content migration source hash mismatch"))
		}
		if len(files) >= limits.MaxFiles || int64(len(data)) > limits.MaxFileBytes || total > limits.MaxTotalBytes-int64(len(data)) {
			return migration.Export{}, invalidRolePackageContract("role content migration source exceeds bounds")
		}
		total += int64(len(data))
		files = append(files, migration.File{Path: assetPath, Data: data})
	}
	if len(files) == 0 || owner == "" {
		return migration.Export{}, invalidRolePackageContract("role content migration source is empty")
	}
	return migration.Export{Files: files}, nil
}

const (
	roleMigrationResponseEnvelopeBytes   = int64(4 << 10)
	roleMigrationRepositoryResponseBytes = int64(64 << 10)
)

func (s AGSHTTPRolePackageContentStore) resolveExactRolePackage(ctx context.Context, contentRef string, maxDecodedBytes int64) (string, error) {
	requestPath, err := s.resolveRequestPath(contentRef)
	if err != nil || maxDecodedBytes <= 0 {
		return "", errRolePackageContentRequired()
	}
	maxEncodedBytes := ((maxDecodedBytes + 2) / 3) * 4
	status, body, err := s.doJSONBounded(ctx, http.MethodGet, requestPath, maxEncodedBytes+roleMigrationResponseEnvelopeBytes)
	if err != nil {
		return "", rolePackageDependencyError(fmt.Errorf("resolve exact role package content: %w", err))
	}
	if status < 200 || status >= 300 {
		return "", rolePackageDependencyError(fmt.Errorf("resolve exact role package content: status %d", status))
	}
	var response struct {
		Type     string `json:"type"`
		Encoding string `json:"encoding"`
		Content  string `json:"content"`
	}
	if err := json.Unmarshal(body, &response); err != nil {
		return "", rolePackageDependencyError(fmt.Errorf("resolve exact role package content: decode response: %w", err))
	}
	if strings.TrimSpace(response.Type) != "file" || strings.TrimSpace(response.Encoding) != "base64" {
		return "", invalidRolePackageContract("role content migration source must be a base64 file")
	}
	encoded := removeASCIIWhitespace(response.Content)
	if int64(len(encoded)) > maxEncodedBytes {
		return "", invalidRolePackageContract("role content migration source exceeds bounds")
	}
	decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encoded))
	decoded, err := io.ReadAll(io.LimitReader(decoder, maxDecodedBytes+1))
	if err != nil {
		return "", rolePackageDependencyError(fmt.Errorf("resolve exact role package content: decode content: %w", err))
	}
	if int64(len(decoded)) > maxDecodedBytes {
		return "", invalidRolePackageContract("role content migration source exceeds bounds")
	}
	return string(decoded), nil
}

type exactRolePackageLocation struct {
	Owner      string
	Repository string
	Path       string
	Ref        string
}

func exactRolePackageLegacyRef(ref, commitSHA string) (exactRolePackageLocation, error) {
	parsed, err := url.Parse(strings.TrimSpace(ref))
	if err != nil || parsed.Scheme == rolePackageRefScheme || parsed.User != nil || parsed.Fragment != "" {
		return exactRolePackageLocation{}, fmt.Errorf("invalid legacy role package ref")
	}
	parts := strings.Split(strings.Trim(parsed.Path, "/"), "/")
	if len(parts) < 7 || parts[0] != "api" || parts[1] != "v3" || parts[2] != "repos" || parts[5] != "contents" {
		return exactRolePackageLocation{}, fmt.Errorf("invalid legacy role package ref path")
	}
	owner, err := url.PathUnescape(parts[3])
	if err != nil {
		return exactRolePackageLocation{}, err
	}
	repository, err := url.PathUnescape(parts[4])
	if err != nil {
		return exactRolePackageLocation{}, err
	}
	pathParts := make([]string, 0, len(parts)-6)
	for _, part := range parts[6:] {
		decoded, decodeErr := url.PathUnescape(part)
		if decodeErr != nil || decoded == "" || decoded == "." || decoded == ".." || strings.Contains(decoded, "\\") {
			return exactRolePackageLocation{}, fmt.Errorf("invalid legacy role package asset path")
		}
		pathParts = append(pathParts, decoded)
	}
	query := parsed.Query()
	query.Set("ref", strings.ToLower(strings.TrimSpace(commitSHA)))
	parsed.RawQuery = query.Encode()
	resolvedRef := parsed.String()
	if parsed.Scheme == "" && parsed.Host == "" {
		resolvedRef = parsed.EscapedPath() + "?" + parsed.RawQuery
	}
	return exactRolePackageLocation{Owner: owner, Repository: repository, Path: strings.Join(pathParts, "/"), Ref: resolvedRef}, nil
}

func (s AGSHTTPRolePackageContentStore) requireExactRolePackageRepository(ctx context.Context, owner, repository, expectedID string) error {
	status, body, err := s.doJSONBounded(ctx, http.MethodGet, "/api/v3/repos/"+url.PathEscape(owner)+"/"+url.PathEscape(repository), roleMigrationRepositoryResponseBytes)
	if err != nil {
		return rolePackageDependencyError(fmt.Errorf("inspect role package repository: %w", err))
	}
	if status < 200 || status >= 300 {
		return rolePackageDependencyError(fmt.Errorf("inspect role package repository: status %d", status))
	}
	var response struct {
		ID int64 `json:"id"`
	}
	if err := json.Unmarshal(body, &response); err != nil || response.ID <= 0 || strconv.FormatInt(response.ID, 10) != strings.TrimSpace(expectedID) {
		return invalidRolePackageContract("role content migration repository identity does not match")
	}
	return nil
}
