package provider

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

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

var (
	ErrUnauthorized             = storeerr.ErrUnauthorized
	ErrForbidden                = storeerr.ErrForbidden
	ErrNotFound                 = storeerr.ErrNotFound
	ErrConflict                 = storeerr.ErrConflict
	ErrBadRequest               = storeerr.ErrBadRequest
	ErrGone                     = storeerr.ErrGone
	ErrSystemNotInitialized     = storeerr.ErrSystemNotInitialized
	ErrSystemAlreadyInitialized = storeerr.ErrSystemAlreadyInitialized
)

func badRequest(message string) error { return storeerr.BadRequest(message) }
func forbidden(message string) error  { return storeerr.Forbidden(message) }
func notFound(message string) error   { return storeerr.NotFound(message) }
func conflict(message string) error   { return storeerr.Conflict(message) }
func gone(message string) error       { return storeerr.Gone(message) }

func rollback(tx coredb.Tx, ctx context.Context) {
	if tx != nil {
		_ = tx.Rollback(ctx)
	}
}

type AppConnectionView struct {
	ID                 string
	ExternalSubjectRef string
	DisplayLabel       string
	RequiresUpgrade    bool
	MissingPermissions []string
	MissingEvents      []string
	Enabled            bool
	CreatedAt          time.Time
	UpdatedAt          time.Time
}

type AppInstallationView struct {
	ID          string
	AppID       string
	AppSlug     string
	AppName     string
	AvatarURL   string
	MemberID    string
	Handle      string
	Connections []AppConnectionView
	Enabled     bool
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

type UpsertAppConnectionParams struct {
	InstallationID     string
	ExternalSubjectRef string
	DisplayLabel       string
	Credentials        map[string]any
	CreatedByUserID    string
}

type StoredAppInstallation struct {
	ID          string
	AppID       string
	WorkspaceID string
	MemberID    string
	Enabled     bool
}

type Options struct {
	AuthorizeWorkspaceAccess             func(context.Context, string, string) error
	AuthorizeWorkspaceServiceInstall     func(context.Context, string, string) error
	EncryptGitHubSecret                  func(string) (string, error)
	EncryptOptionalGitHubSecret          func(string) (string, error)
	DecryptGitHubSecret                  func(string) (string, error)
	EncryptGiteaSecret                   func(string) (string, error)
	DecryptGiteaSecret                   func(string) (string, error)
	FindAppInstallationByWorkspaceAndApp func(context.Context, string, string) (StoredAppInstallation, bool, error)
	UpsertAppConnection                  func(context.Context, UpsertAppConnectionParams) (AppConnectionView, error)
	UpsertAppConnectionInTx              func(context.Context, coredb.Tx, UpsertAppConnectionParams) (AppConnectionView, error)
	ListAppConnections                   func(context.Context, string) ([]AppConnectionView, error)
	DisableAppConnection                 func(context.Context, string, string) error
	DisableAppConnectionInTx             func(context.Context, coredb.Tx, string, string) error
	SetGitHubSubscriptionsBlockedInTx    func(context.Context, coredb.Tx, string, string, bool, time.Time) error
	AppInstallationViewByID              func(context.Context, string, string) (AppInstallationView, error)
}

type Store struct {
	db      coredb.DB
	options Options
}

func New(db coredb.DB, options Options) *Store {
	return &Store{db: db, options: options}
}

func (s *Store) Validate() error { return s.options.Validate() }

func (o Options) Validate() error {
	var missing []string
	if o.AuthorizeWorkspaceAccess == nil {
		missing = append(missing, "AuthorizeWorkspaceAccess")
	}
	if o.AuthorizeWorkspaceServiceInstall == nil {
		missing = append(missing, "AuthorizeWorkspaceServiceInstall")
	}
	if o.EncryptGitHubSecret == nil {
		missing = append(missing, "EncryptGitHubSecret")
	}
	if o.EncryptOptionalGitHubSecret == nil {
		missing = append(missing, "EncryptOptionalGitHubSecret")
	}
	if o.DecryptGitHubSecret == nil {
		missing = append(missing, "DecryptGitHubSecret")
	}
	if o.EncryptGiteaSecret == nil {
		missing = append(missing, "EncryptGiteaSecret")
	}
	if o.DecryptGiteaSecret == nil {
		missing = append(missing, "DecryptGiteaSecret")
	}
	if o.FindAppInstallationByWorkspaceAndApp == nil {
		missing = append(missing, "FindAppInstallationByWorkspaceAndApp")
	}
	if o.UpsertAppConnection == nil {
		missing = append(missing, "UpsertAppConnection")
	}
	if o.UpsertAppConnectionInTx == nil {
		missing = append(missing, "UpsertAppConnectionInTx")
	}
	if o.ListAppConnections == nil {
		missing = append(missing, "ListAppConnections")
	}
	if o.DisableAppConnection == nil {
		missing = append(missing, "DisableAppConnection")
	}
	if o.DisableAppConnectionInTx == nil {
		missing = append(missing, "DisableAppConnectionInTx")
	}
	if o.SetGitHubSubscriptionsBlockedInTx == nil {
		missing = append(missing, "SetGitHubSubscriptionsBlockedInTx")
	}
	if o.AppInstallationViewByID == nil {
		missing = append(missing, "AppInstallationViewByID")
	}
	if len(missing) > 0 {
		return fmt.Errorf("provider store: missing dependencies: %s", strings.Join(missing, ", "))
	}
	return nil
}

func (store *Store) requireWorkspaceAccess(ctx context.Context, userID string, workspaceID string) error {
	if store == nil || store.options.AuthorizeWorkspaceAccess == nil {
		return fmt.Errorf("provider store authorize workspace access dependency is not configured")
	}
	return store.options.AuthorizeWorkspaceAccess(ctx, userID, workspaceID)
}

func (store *Store) requireWorkspaceServiceInstall(ctx context.Context, userID string, workspaceID string) error {
	if store == nil || store.options.AuthorizeWorkspaceServiceInstall == nil {
		return fmt.Errorf("provider store authorize workspace service install dependency is not configured")
	}
	return store.options.AuthorizeWorkspaceServiceInstall(ctx, userID, workspaceID)
}

func (store *Store) encryptGitHubAppConfigSecret(secret string) (string, error) {
	if store == nil || store.options.EncryptGitHubSecret == nil {
		return "", fmt.Errorf("provider store github encryption dependency is not configured")
	}
	return store.options.EncryptGitHubSecret(secret)
}

func (store *Store) encryptOptionalGitHubAppConfigSecret(secret string) (string, error) {
	if store == nil || store.options.EncryptOptionalGitHubSecret == nil {
		return "", fmt.Errorf("provider store github optional encryption dependency is not configured")
	}
	return store.options.EncryptOptionalGitHubSecret(secret)
}

func (store *Store) decryptGitHubAppConfigSecret(stored string) (string, error) {
	if store == nil || store.options.DecryptGitHubSecret == nil {
		return "", fmt.Errorf("provider store github decryption dependency is not configured")
	}
	return store.options.DecryptGitHubSecret(stored)
}

func (store *Store) encryptGiteaAppConfigSecret(secret string) (string, error) {
	if store == nil || store.options.EncryptGiteaSecret == nil {
		return "", fmt.Errorf("provider store gitea encryption dependency is not configured")
	}
	return store.options.EncryptGiteaSecret(secret)
}

func (store *Store) decryptGiteaAppConfigSecret(stored string) (string, error) {
	if store == nil || store.options.DecryptGiteaSecret == nil {
		return "", fmt.Errorf("provider store gitea decryption dependency is not configured")
	}
	return store.options.DecryptGiteaSecret(stored)
}

func (store *Store) findAppInstallationByWorkspaceAndApp(ctx context.Context, workspaceID string, appID string) (StoredAppInstallation, bool, error) {
	if store == nil || store.options.FindAppInstallationByWorkspaceAndApp == nil {
		return StoredAppInstallation{}, false, fmt.Errorf("provider store app installation lookup dependency is not configured")
	}
	return store.options.FindAppInstallationByWorkspaceAndApp(ctx, workspaceID, appID)
}

func (store *Store) UpsertAppConnection(ctx context.Context, params UpsertAppConnectionParams) (AppConnectionView, error) {
	if store == nil || store.options.UpsertAppConnection == nil {
		return AppConnectionView{}, fmt.Errorf("provider store upsert app connection dependency is not configured")
	}
	return store.options.UpsertAppConnection(ctx, params)
}

func (store *Store) upsertAppConnectionInTx(ctx context.Context, tx coredb.Tx, params UpsertAppConnectionParams) (AppConnectionView, error) {
	if store == nil || store.options.UpsertAppConnectionInTx == nil {
		return AppConnectionView{}, fmt.Errorf("provider store transactional upsert app connection dependency is not configured")
	}
	return store.options.UpsertAppConnectionInTx(ctx, tx, params)
}

func (store *Store) ListAppConnections(ctx context.Context, installationID string) ([]AppConnectionView, error) {
	if store == nil || store.options.ListAppConnections == nil {
		return nil, fmt.Errorf("provider store list app connections dependency is not configured")
	}
	return store.options.ListAppConnections(ctx, installationID)
}

func (store *Store) DisableAppConnection(ctx context.Context, installationID string, connectionID string) error {
	if store == nil || store.options.DisableAppConnection == nil {
		return fmt.Errorf("provider store disable app connection dependency is not configured")
	}
	return store.options.DisableAppConnection(ctx, installationID, connectionID)
}

func (store *Store) disableAppConnectionInTx(ctx context.Context, tx coredb.Tx, installationID string, connectionID string) error {
	if store == nil || store.options.DisableAppConnectionInTx == nil {
		return fmt.Errorf("provider store transactional disable app connection dependency is not configured")
	}
	return store.options.DisableAppConnectionInTx(ctx, tx, installationID, connectionID)
}

func (store *Store) setGitHubSubscriptionsBlockedInTx(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	connectionRef string,
	blocked bool,
	now time.Time,
) error {
	if store == nil || store.options.SetGitHubSubscriptionsBlockedInTx == nil {
		return fmt.Errorf("provider store transactional github subscription blocker dependency is not configured")
	}
	return store.options.SetGitHubSubscriptionsBlockedInTx(ctx, tx, workspaceID, connectionRef, blocked, now)
}

func (store *Store) appInstallationViewByID(ctx context.Context, workspaceID string, installationID string) (AppInstallationView, error) {
	if store == nil || store.options.AppInstallationViewByID == nil {
		return AppInstallationView{}, fmt.Errorf("provider store app installation view dependency is not configured")
	}
	return store.options.AppInstallationViewByID(ctx, workspaceID, installationID)
}
