package provider

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

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

const (
	providerGitHubAppClaimStateSetup    = "setup"
	providerGitHubAppClaimStatePatching = "patching"
	providerGitHubAppClaimStateActive   = "active"
)

type providerGitHubAppClaim struct {
	appID       int64
	workspaceID string
	state       string
	token       string
}

// prepareGitHubAppClaimForConfigSaveInTx makes config writes fail closed.
// Generic writes may create or reuse only an active claim; setup finalizers
// must present the exact durable lease/proposal token that authorized them.
func prepareGitHubAppClaimForConfigSaveInTx(
	ctx context.Context,
	tx coredb.Tx,
	appID int64,
	workspaceID string,
	claimToken string,
	now time.Time,
) error {
	trimmedWorkspaceID := strings.TrimSpace(workspaceID)
	trimmedToken := strings.TrimSpace(claimToken)
	if appID <= 0 || trimmedWorkspaceID == "" {
		return badRequest("GitHub App ownership is invalid.")
	}
	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
`, providerGitHubAppClaimStateSetup, now.UTC()); err != nil {
		return fmt.Errorf("delete expired GitHub App claims: %w", err)
	}
	claim, found, err := loadProviderGitHubAppClaimByAppIDForUpdate(ctx, tx, appID)
	if err != nil {
		return err
	}
	if found && claim.state == providerGitHubAppClaimStateSetup {
		if _, err := tx.Exec(ctx, `
DELETE FROM service_provider_workspace_claims
WHERE provider_app_id = $1
  AND claim_state = $2
`, appID, providerGitHubAppClaimStateSetup); err != nil {
			return fmt.Errorf("remove legacy GitHub App setup claim: %w", err)
		}
		found = false
		claim = providerGitHubAppClaim{}
	}
	if trimmedToken != "" {
		if !found || claim.workspaceID != trimmedWorkspaceID || claim.token != trimmedToken ||
			claim.state != providerGitHubAppClaimStatePatching {
			return conflict("GitHub App setup ownership changed. Restart setup.")
		}
		return nil
	}
	if found {
		if claim.workspaceID != trimmedWorkspaceID {
			return conflict("This GitHub App is already owned by another AgentTeam workspace.")
		}
		if claim.state != providerGitHubAppClaimStateActive {
			return conflict("GitHub App webhook setup is in progress and requires its exact completion lease.")
		}
		return nil
	}

	workspaceClaim, workspaceClaimed, err := loadProviderGitHubAppClaimByWorkspaceForUpdate(ctx, tx, trimmedWorkspaceID)
	if err != nil {
		return err
	}
	if workspaceClaimed && workspaceClaim.state == providerGitHubAppClaimStateSetup {
		if _, err := tx.Exec(ctx, `
DELETE FROM service_provider_workspace_claims
WHERE workspace_id = $1
  AND claim_state = $2
`, trimmedWorkspaceID, providerGitHubAppClaimStateSetup); err != nil {
			return fmt.Errorf("remove legacy workspace GitHub App setup claim: %w", err)
		}
		workspaceClaimed = false
	}
	if workspaceClaimed {
		return conflict("A different GitHub App is already owned by this AgentTeam workspace.")
	}
	if _, 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, '', NULL, $4, $4)
`, appID, trimmedWorkspaceID, providerGitHubAppClaimStateActive, now.UTC()); err != nil {
		if coredb.IsUniqueViolation(err) {
			return conflict("This GitHub App is already owned by another AgentTeam workspace.")
		}
		return fmt.Errorf("claim active GitHub App: %w", err)
	}
	return nil
}

func activateGitHubAppClaimAfterConfigSaveInTx(
	ctx context.Context,
	tx coredb.Tx,
	appID int64,
	workspaceID string,
	claimToken string,
	now time.Time,
) error {
	trimmedToken := strings.TrimSpace(claimToken)
	if trimmedToken == "" {
		claim, found, err := loadProviderGitHubAppClaimByAppIDForUpdate(ctx, tx, appID)
		if err != nil {
			return err
		}
		if !found || claim.workspaceID != strings.TrimSpace(workspaceID) || claim.state != providerGitHubAppClaimStateActive {
			return conflict("GitHub App active ownership changed.")
		}
		return nil
	}
	result, err := tx.Exec(ctx, `
UPDATE service_provider_workspace_claims
SET claim_state = $1,
    claim_token = '',
    expires_at = NULL,
    updated_at = $2
WHERE provider_app_id = $3
  AND workspace_id = $4
	AND claim_state = $5
	AND claim_token = $6
`, providerGitHubAppClaimStateActive, now.UTC(), appID, strings.TrimSpace(workspaceID),
		providerGitHubAppClaimStatePatching, trimmedToken)
	if err != nil {
		return fmt.Errorf("activate GitHub App claim: %w", err)
	}
	if result.RowsAffected() != 1 {
		return conflict("GitHub App setup ownership changed. Restart setup.")
	}
	return nil
}

func releaseProviderActiveGitHubAppClaimInTx(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), providerGitHubAppClaimStateActive); err != nil {
		return fmt.Errorf("release active GitHub App claim: %w", err)
	}
	return nil
}

func loadProviderGitHubAppClaimByAppIDForUpdate(
	ctx context.Context,
	queryer coredb.QueryRower,
	appID int64,
) (providerGitHubAppClaim, bool, error) {
	var claim providerGitHubAppClaim
	err := queryer.QueryRow(ctx, `
SELECT provider_app_id, workspace_id, claim_state, claim_token
FROM service_provider_workspace_claims
WHERE provider_app_id = $1
LIMIT 1
FOR UPDATE
`, appID).Scan(&claim.appID, &claim.workspaceID, &claim.state, &claim.token)
	if err != nil {
		if coredb.IsNotFound(err) {
			return providerGitHubAppClaim{}, false, nil
		}
		return providerGitHubAppClaim{}, false, fmt.Errorf("load 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
}

func loadProviderGitHubAppClaimByWorkspaceForUpdate(
	ctx context.Context,
	queryer coredb.QueryRower,
	workspaceID string,
) (providerGitHubAppClaim, bool, error) {
	var claim providerGitHubAppClaim
	err := queryer.QueryRow(ctx, `
SELECT provider_app_id, workspace_id, claim_state, claim_token
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)
	if err != nil {
		if coredb.IsNotFound(err) {
			return providerGitHubAppClaim{}, false, nil
		}
		return providerGitHubAppClaim{}, 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
}
