package app

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

const (
	githubAppClaimStateSetup    = "setup"
	githubAppClaimStatePatching = "patching"
	githubAppClaimStateActive   = "active"
)

type githubAppWorkspaceClaim struct {
	AppID       int64
	WorkspaceID string
	State       string
	Token       string
	ExpiresAt   *time.Time
}

// markGitHubAppClaimPatchingInTx is the first authoritative ownership point
// for a fresh setup and the point of no return before provider PATCH. Drafts,
// verification reads, and pending approvals deliberately hold no claim. The
// first contender to insert this durable patching row wins; every different
// token must fail before provider mutation. For an active-config update,
// allowActive moves the existing active owner into the same durable lease.
// Uncommitted setup rows are advisory reservations and may be discarded here,
// but patching and active rows are never preempted.
func markGitHubAppClaimPatchingInTx(
	ctx context.Context,
	tx coredb.Tx,
	appID int64,
	workspaceID string,
	token string,
	allowActive bool,
	now time.Time,
) error {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	trimmedToken := strings.TrimSpace(token)
	if appID <= 0 || trimmedWorkspaceID == "" || trimmedToken == "" {
		return conflict("GitHub App ownership is invalid. Restart setup.")
	}
	now = now.UTC()

	claim, found, err := loadGitHubAppWorkspaceClaimByAppIDForUpdate(ctx, tx, appID)
	if err != nil {
		return err
	}
	if found {
		if claim.State == githubAppClaimStatePatching &&
			claim.WorkspaceID == trimmedWorkspaceID && claim.Token == trimmedToken {
			return nil
		}
		switch claim.State {
		case githubAppClaimStateSetup:
			if _, err := tx.Exec(ctx, `
DELETE FROM service_provider_workspace_claims
WHERE provider_app_id = $1
  AND claim_state = $2
`, appID, githubAppClaimStateSetup); err != nil {
				return fmt.Errorf("remove stale GitHub App setup claim: %w", err)
			}
		case githubAppClaimStateActive:
			if !allowActive || claim.WorkspaceID != trimmedWorkspaceID {
				return conflict("This GitHub App is already active for a AgentTeam workspace.")
			}
			result, err := tx.Exec(ctx, `
UPDATE service_provider_workspace_claims
SET claim_state = $1,
    claim_token = $2,
    expires_at = NULL,
    updated_at = $3
WHERE provider_app_id = $4
  AND workspace_id = $5
  AND claim_state = $6
  AND claim_token = ''
`, githubAppClaimStatePatching, trimmedToken, now, appID, trimmedWorkspaceID,
				githubAppClaimStateActive)
			if err != nil {
				return fmt.Errorf("mark active GitHub App webhook mutation started: %w", err)
			}
			if result.RowsAffected() != 1 {
				return conflict("GitHub App active ownership changed. Restart setup.")
			}
			return nil
		case githubAppClaimStatePatching:
			return conflict("Another GitHub App installation already won the activation race.")
		default:
			return conflict("GitHub App ownership state is invalid.")
		}
	}

	workspaceClaim, workspaceClaimed, err := loadGitHubAppWorkspaceClaimByWorkspaceForUpdate(
		ctx, tx, trimmedWorkspaceID,
	)
	if err != nil {
		return err
	}
	if workspaceClaimed {
		if workspaceClaim.State == githubAppClaimStateSetup {
			if _, err := tx.Exec(ctx, `
DELETE FROM service_provider_workspace_claims
WHERE workspace_id = $1
  AND claim_state = $2
`, trimmedWorkspaceID, githubAppClaimStateSetup); err != nil {
				return fmt.Errorf("remove stale workspace GitHub App setup claim: %w", err)
			}
		} else {
			return conflict("Another GitHub App installation already won the activation race.")
		}
	}

	_, err = tx.Exec(ctx, `
INSERT INTO service_provider_workspace_claims (
    provider_app_id, workspace_id, claim_state, claim_token,
    expires_at, created_at, updated_at
) VALUES ($1, $2, $3, $4, NULL, $5, $5)
`, appID, trimmedWorkspaceID, githubAppClaimStatePatching, trimmedToken, now)
	if err != nil {
		if coredb.IsUniqueViolation(err) {
			return conflict("Another GitHub App installation already won the activation race.")
		}
		return fmt.Errorf("claim GitHub App provider mutation: %w", err)
	}
	return nil
}

func requireGitHubAppPatchingClaimForUpdate(
	ctx context.Context,
	tx coredb.Tx,
	appID int64,
	workspaceID string,
	token string,
) error {
	claim, found, err := loadGitHubAppWorkspaceClaimByAppIDForUpdate(ctx, tx, appID)
	if err != nil {
		return err
	}
	if !found || claim.WorkspaceID != strings.TrimSpace(workspaceID) ||
		claim.State != githubAppClaimStatePatching || claim.Token != strings.TrimSpace(token) {
		return conflict("GitHub App webhook completion is not recoverable. Restart setup.")
	}
	return nil
}

func requireGitHubAppActiveClaimForUpdate(
	ctx context.Context,
	tx coredb.Tx,
	appID int64,
	workspaceID string,
) error {
	claim, found, err := loadGitHubAppWorkspaceClaimByAppIDForUpdate(ctx, tx, appID)
	if err != nil {
		return err
	}
	if !found || claim.AppID != appID ||
		claim.WorkspaceID != strings.TrimSpace(workspaceID) ||
		claim.State != githubAppClaimStateActive || claim.Token != "" || claim.ExpiresAt != nil {
		return conflict("GitHub App active ownership changed. Restart setup.")
	}
	return nil
}

func releaseGitHubAppSetupClaimByTokenInTx(ctx context.Context, tx coredb.Tx, workspaceID string, token string) error {
	if strings.TrimSpace(token) == "" {
		return nil
	}
	if _, err := tx.Exec(ctx, `
DELETE FROM service_provider_workspace_claims
WHERE workspace_id = $1
  AND claim_state = $2
  AND claim_token = $3
`, strings.TrimSpace(workspaceID), githubAppClaimStateSetup, strings.TrimSpace(token)); err != nil {
		return fmt.Errorf("release GitHub App setup claim: %w", err)
	}
	return nil
}

func releaseActiveGitHubAppClaimForWorkspaceInTx(ctx context.Context, tx coredb.Tx, workspaceID string) error {
	if _, err := tx.Exec(ctx, `
DELETE FROM service_provider_workspace_claims
WHERE workspace_id = $1
  AND claim_state = $2
`, strings.TrimSpace(workspaceID), githubAppClaimStateActive); err != nil {
		return fmt.Errorf("release active GitHub App claim: %w", err)
	}
	return nil
}

func deleteExpiredGitHubAppSetupClaimsInTx(ctx context.Context, tx coredb.Tx, now time.Time) error {
	if _, err := tx.Exec(ctx, `
DELETE FROM service_provider_workspace_claims
WHERE claim_state = $1
  AND expires_at IS NOT NULL
  AND expires_at < $2
`, githubAppClaimStateSetup, now.UTC()); err != nil {
		return fmt.Errorf("delete expired GitHub App claims: %w", err)
	}
	return nil
}

func loadGitHubAppWorkspaceClaimByAppIDForUpdate(
	ctx context.Context,
	queryer coredb.QueryRower,
	appID int64,
) (githubAppWorkspaceClaim, bool, error) {
	var claim githubAppWorkspaceClaim
	err := queryer.QueryRow(ctx, `
SELECT provider_app_id, workspace_id, claim_state, claim_token, expires_at
FROM service_provider_workspace_claims
WHERE provider_app_id = $1
LIMIT 1
FOR UPDATE
`, appID).Scan(&claim.AppID, &claim.WorkspaceID, &claim.State, &claim.Token, &claim.ExpiresAt)
	if err != nil {
		if coredb.IsNotFound(err) {
			return githubAppWorkspaceClaim{}, false, nil
		}
		return githubAppWorkspaceClaim{}, false, fmt.Errorf("load GitHub App workspace claim: %w", err)
	}
	claim.WorkspaceID = strings.TrimSpace(claim.WorkspaceID)
	claim.State = strings.TrimSpace(claim.State)
	claim.Token = strings.TrimSpace(claim.Token)
	return claim, true, nil
}

func loadGitHubAppWorkspaceClaimByWorkspaceForUpdate(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
) (githubAppWorkspaceClaim, bool, error) {
	var claim githubAppWorkspaceClaim
	err := queryer.QueryRow(ctx, `
SELECT provider_app_id, workspace_id, claim_state, claim_token, expires_at
FROM service_provider_workspace_claims
WHERE workspace_id = $1
LIMIT 1
FOR UPDATE
`, strings.TrimSpace(workspaceID)).Scan(
		&claim.AppID, &claim.WorkspaceID, &claim.State, &claim.Token, &claim.ExpiresAt,
	)
	if err != nil {
		if coredb.IsNotFound(err) {
			return githubAppWorkspaceClaim{}, false, nil
		}
		return githubAppWorkspaceClaim{}, false, fmt.Errorf("load workspace GitHub App claim: %w", err)
	}
	claim.WorkspaceID = strings.TrimSpace(claim.WorkspaceID)
	claim.State = strings.TrimSpace(claim.State)
	claim.Token = strings.TrimSpace(claim.Token)
	return claim, true, nil
}
