package app

import (
	"fmt"
	"slices"
	"strconv"
	"strings"
	"unicode/utf8"
)

const (
	githubConnectionTargetsMax         = 50
	githubConnectionTargetsSnapshotKey = "_agentteam_github_installations"
	githubConnectionTargetsVersion     = "v1"
	githubConnectionAccountLoginMax    = 255
)

type githubConnectionTargetSnapshot struct {
	InstallationID string
	AccountLogin   string
	AccountID      string
}

func normalizedGitHubInstallationIDs(values []string) ([]string, error) {
	if len(values) == 0 {
		return nil, badRequest("At least one GitHub installation id is required.")
	}
	if len(values) > githubConnectionTargetsMax {
		return nil, badRequest(fmt.Sprintf("At most %d GitHub installations may be selected.", githubConnectionTargetsMax))
	}
	type parsedTarget struct {
		id    int64
		value string
	}
	parsed := make([]parsedTarget, 0, len(values))
	seen := make(map[string]struct{}, len(values))
	for _, value := range values {
		canonical, err := canonicalGitHubInstallationID(value)
		if err != nil {
			return nil, err
		}
		if _, exists := seen[canonical]; exists {
			return nil, badRequest("GitHub installation ids must be unique.")
		}
		seen[canonical] = struct{}{}
		id, _ := strconv.ParseInt(canonical, 10, 64)
		parsed = append(parsed, parsedTarget{id: id, value: canonical})
	}
	slices.SortFunc(parsed, func(left, right parsedTarget) int {
		switch {
		case left.id < right.id:
			return -1
		case left.id > right.id:
			return 1
		default:
			return 0
		}
	})
	result := make([]string, 0, len(parsed))
	for _, target := range parsed {
		result = append(result, target.value)
	}
	return result, nil
}

func normalizeGitHubInstallationSelection(single string, multiple []string) ([]string, error) {
	values := append([]string(nil), multiple...)
	if strings.TrimSpace(single) != "" {
		values = append(values, single)
	}
	return normalizedGitHubInstallationIDs(values)
}

func githubConnectionTargetSnapshotMap(targets []githubConnectionTargetSnapshot) map[string]any {
	installations := make([]any, 0, len(targets))
	for _, target := range targets {
		installations = append(installations, map[string]any{
			"installation_id": strings.TrimSpace(target.InstallationID),
			"account_login":   strings.TrimSpace(target.AccountLogin),
			"account_id":      strings.TrimSpace(target.AccountID),
		})
	}
	return map[string]any{
		"version":       githubConnectionTargetsVersion,
		"installations": installations,
	}
}

func parseGitHubConnectionTargetSnapshots(stored map[string]any) ([]githubConnectionTargetSnapshot, error) {
	raw, exists := stored[githubConnectionTargetsSnapshotKey]
	if !exists {
		legacy := requestSnapshotString(stored, connectionRequestTargetKey)
		if legacy == "" {
			return nil, nil
		}
		canonical, err := canonicalGitHubInstallationID(legacy)
		if err != nil {
			return nil, conflict("GitHub installation selection is invalid. Submit a new request.")
		}
		return []githubConnectionTargetSnapshot{{
			InstallationID: canonical,
			AccountLogin:   requestSnapshotString(stored, connectionRequestDisplayLabelKey),
		}}, nil
	}
	envelope, ok := raw.(map[string]any)
	if !ok || len(envelope) != 2 || requestSnapshotString(envelope, "version") != githubConnectionTargetsVersion {
		return nil, conflict("GitHub installation selection is invalid. Submit a new request.")
	}
	rawInstallations, ok := envelope["installations"].([]any)
	if !ok || len(rawInstallations) == 0 || len(rawInstallations) > githubConnectionTargetsMax {
		return nil, conflict("GitHub installation selection is invalid. Submit a new request.")
	}
	targets := make([]githubConnectionTargetSnapshot, 0, len(rawInstallations))
	ids := make([]string, 0, len(rawInstallations))
	for _, rawInstallation := range rawInstallations {
		item, ok := rawInstallation.(map[string]any)
		if !ok || len(item) != 3 {
			return nil, conflict("GitHub installation selection is invalid. Submit a new request.")
		}
		id := requestSnapshotString(item, "installation_id")
		login := requestSnapshotString(item, "account_login")
		accountID := requestSnapshotString(item, "account_id")
		canonical, err := canonicalGitHubInstallationID(id)
		if err != nil || login == "" || utf8.RuneCountInString(login) > githubConnectionAccountLoginMax {
			return nil, conflict("GitHub installation selection is invalid. Submit a new request.")
		}
		// Versioned snapshots are the server-derived, fail-closed authority used
		// when an approval is retried. Unlike the legacy scalar fallback above,
		// v1 must carry GitHub's immutable positive account id so a renamed or
		// replaced account cannot be accepted merely because its label matches.
		parsedAccountID, parseErr := strconv.ParseInt(accountID, 10, 64)
		if parseErr != nil || parsedAccountID <= 0 || strconv.FormatInt(parsedAccountID, 10) != accountID {
			return nil, conflict("GitHub installation selection is invalid. Submit a new request.")
		}
		ids = append(ids, canonical)
		targets = append(targets, githubConnectionTargetSnapshot{
			InstallationID: canonical,
			AccountLogin:   login,
			AccountID:      accountID,
		})
	}
	canonicalIDs, err := normalizedGitHubInstallationIDs(ids)
	if err != nil || !slices.Equal(ids, canonicalIDs) {
		return nil, conflict("GitHub installation selection is invalid. Submit a new request.")
	}
	return targets, nil
}

func githubConnectionTargetIDs(targets []githubConnectionTargetSnapshot) []string {
	result := make([]string, 0, len(targets))
	for _, target := range targets {
		result = append(result, target.InstallationID)
	}
	return result
}

func sameGitHubConnectionTargetIdentities(
	left []githubConnectionTargetSnapshot,
	right []githubConnectionTargetSnapshot,
) bool {
	if len(left) != len(right) {
		return false
	}
	for index := range left {
		if left[index].InstallationID != right[index].InstallationID ||
			left[index].AccountID == "" || left[index].AccountID != right[index].AccountID {
			return false
		}
	}
	return true
}

// appInstallationRequestTerminalRequestedConfig builds the durable audit
// projection for a terminal request. GitHub initial-install rows must retain
// their safe, exact target set for later reviewer/audit reads, while every
// credential envelope (and even its masked placeholder) is discarded.
func appInstallationRequestTerminalRequestedConfig(
	request AppInstallationRequestView,
) (map[string]any, error) {
	if !isGitHubApp(request.AppID) ||
		strings.TrimSpace(request.RequestKind) != AppInstallationRequestKindInstall {
		return cloneAppInstallationRequestConfig(request.RequestedConfig), nil
	}
	stored := appInstallationRequestStoredRequestedConfig(request)
	resolved := appInstallationRequestPublicGitHubConfig(stored)
	for _, key := range []string{
		githubConfigClientSecretKey,
		githubConfigPrivateKeyPEMKey,
		githubConfigWebhookSecretKey,
	} {
		delete(resolved, key)
	}
	targets, err := parseGitHubConnectionTargetSnapshots(stored)
	if err != nil {
		return nil, err
	}
	if len(targets) > 0 {
		resolved[githubConnectionTargetsSnapshotKey] = githubConnectionTargetSnapshotMap(targets)
	}
	return resolved, nil
}
