package integrations

import (
	"context"
	"fmt"
	"log"
	"path/filepath"
	"sync"
	"time"

	"gitea.dooplex.hu/admin/felhom-controller/internal/crypto"
	"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
	"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)

// StackProvider abstracts stacks.Manager to break circular imports.
type StackProvider interface {
	GetStack(name string) (*stacks.Stack, bool)
	GetStacks() []stacks.Stack
	RestartStack(name string) error
}

// Manager coordinates integration apply/revoke and lifecycle hooks.
type Manager struct {
	sett      *settings.Settings
	stacks    StackProvider
	domain    string
	stacksDir string
	encKey    []byte
	logger    *log.Logger

	handlers map[string]Handler // key: "provider:target" -> Handler
	mu       sync.Mutex         // serialize apply/revoke operations
	debug    bool
}

// NewManager creates an integration manager and registers built-in handlers.
func NewManager(sett *settings.Settings, sp StackProvider, domain, stacksDir string, encKey []byte, logger *log.Logger) *Manager {
	m := &Manager{
		sett:      sett,
		stacks:    sp,
		domain:    domain,
		stacksDir: stacksDir,
		encKey:    encKey,
		logger:    logger,
		handlers:  make(map[string]Handler),
	}
	// Register built-in handlers
	m.RegisterHandler("onlyoffice:filebrowser", &OnlyOfficeFileBrowserHandler{})
	m.RegisterHandler("onlyoffice:nextcloud", &OnlyOfficeNextcloudHandler{})

	if m.isDebug() {
		keys := make([]string, 0, len(m.handlers))
		for k := range m.handlers {
			keys = append(keys, k)
		}
		m.logger.Printf("[DEBUG] [integrations] NewManager: registered handlers: %v", keys)
	}

	return m
}

// SetDebug enables or disables debug logging.
func (m *Manager) SetDebug(debug bool) {
	m.debug = debug
}

// isDebug returns whether debug logging is enabled.
func (m *Manager) isDebug() bool {
	return m.debug
}

// RegisterHandler registers a handler for a provider:target integration key.
func (m *Manager) RegisterHandler(key string, h Handler) {
	m.handlers[key] = h
}

// Toggle enables or disables an integration.
func (m *Manager) Toggle(ctx context.Context, provider, target string, enable bool) (settings.IntegrationState, error) {
	m.mu.Lock()
	defer m.mu.Unlock()

	key := IntegrationKey(provider, target)
	if m.isDebug() {
		m.logger.Printf("[DEBUG] [integrations] Toggle: key=%s provider=%s target=%s enable=%v", key, provider, target, enable)
	}

	handler, ok := m.handlers[key]
	if !ok {
		return settings.IntegrationState{}, fmt.Errorf("nincs kezelő a(z) %s integrációhoz", key)
	}

	ac, err := m.buildApplyContext(provider, target)
	if err != nil {
		return settings.IntegrationState{}, err
	}

	var state settings.IntegrationState
	if enable {
		// Validate: provider must be deployed and running
		provStack, pOk := m.stacks.GetStack(provider)
		if m.isDebug() {
			m.logger.Printf("[DEBUG] [integrations] Toggle: provider %s found=%v deployed=%v state=%v", provider, pOk, pOk && provStack.Deployed, func() stacks.ContainerState { if pOk { return provStack.State }; return "" }())
		}
		if !pOk || !provStack.Deployed {
			return state, fmt.Errorf("a szolgáltató alkalmazás (%s) nincs telepítve", provider)
		}
		if provStack.State != stacks.StateRunning {
			return state, fmt.Errorf("a szolgáltató alkalmazás (%s) nem fut", provider)
		}

		// Validate: target must be deployed and running (filebrowser is infra, always present)
		if target != "filebrowser" {
			tgtStack, tOk := m.stacks.GetStack(target)
			if m.isDebug() {
				m.logger.Printf("[DEBUG] [integrations] Toggle: target %s found=%v deployed=%v state=%v", target, tOk, tOk && tgtStack.Deployed, func() stacks.ContainerState { if tOk { return tgtStack.State }; return "" }())
			}
			if !tOk || !tgtStack.Deployed {
				return state, fmt.Errorf("a célalkalmazás (%s) nincs telepítve", target)
			}
			if tgtStack.State != stacks.StateRunning {
				return state, fmt.Errorf("a célalkalmazás (%s) nem fut", target)
			}
		}

		start := time.Now()
		if err := handler.Apply(ac); err != nil {
			m.logger.Printf("[ERROR] [integrations] Integration %s apply failed: %v", key, err)
			if m.isDebug() {
				m.logger.Printf("[DEBUG] [integrations] Toggle: Apply failed for %s in %v: %v", key, time.Since(start), err)
			}
			state.Enabled = true
			state.Status = "error"
			state.LastError = err.Error()
			state.EnabledAt = time.Now().UTC().Format(time.RFC3339)
			_ = m.sett.SetIntegrationState(key, state)
			return state, fmt.Errorf("integráció alkalmazása sikertelen: %w", err)
		}
		if m.isDebug() {
			m.logger.Printf("[DEBUG] [integrations] Toggle: Apply succeeded for %s in %v", key, time.Since(start))
		}
		state.Enabled = true
		state.Status = "active"
		state.EnabledAt = time.Now().UTC().Format(time.RFC3339)
		m.logger.Printf("[INFO] [integrations] Integration %s enabled", key)
	} else {
		start := time.Now()
		if err := handler.Revoke(ac); err != nil {
			m.logger.Printf("[WARN] [integrations] Integration revoke failed for %s: %v", key, err)
			state.LastError = err.Error()
		}
		if m.isDebug() {
			m.logger.Printf("[DEBUG] [integrations] Toggle: Revoke for %s completed in %v", key, time.Since(start))
		}
		state.Enabled = false
		state.Status = "disabled"
		m.logger.Printf("[INFO] [integrations] Integration %s disabled", key)
	}

	if err := m.sett.SetIntegrationState(key, state); err != nil {
		return state, fmt.Errorf("integráció állapotának mentése sikertelen: %w", err)
	}
	return state, nil
}

// ListForProvider returns StatusInfo for all integrations defined in a provider's metadata.
func (m *Manager) ListForProvider(providerSlug string) []StatusInfo {
	provStack, ok := m.stacks.GetStack(providerSlug)
	if !ok {
		return nil
	}

	if m.isDebug() {
		m.logger.Printf("[DEBUG] [integrations] ListForProvider: provider=%s integrationDefs=%d", providerSlug, len(provStack.Meta.Integrations))
	}

	var result []StatusInfo
	for _, idef := range provStack.Meta.Integrations {
		key := IntegrationKey(providerSlug, idef.Target)
		state, _ := m.sett.GetIntegrationState(key)

		targetDeployed := false
		targetRunning := false
		if tgt, tOk := m.stacks.GetStack(idef.Target); tOk {
			// FileBrowser is infrastructure — if its stack exists, it's "deployed"
			if idef.Target == "filebrowser" {
				targetDeployed = true
			} else {
				targetDeployed = tgt.Deployed
			}
			targetRunning = tgt.State == stacks.StateRunning
		}

		result = append(result, StatusInfo{
			Key:            key,
			Provider:       providerSlug,
			Target:         idef.Target,
			Label:          idef.Label,
			Description:    idef.Description,
			Enabled:        state.Enabled,
			Status:         state.Status,
			LastError:      state.LastError,
			TargetDeployed: targetDeployed,
			TargetRunning:  targetRunning,
		})
	}
	return result
}

// buildApplyContext gathers all data needed by a handler.
func (m *Manager) buildApplyContext(provider, target string) (*ApplyContext, error) {
	provStack, ok := m.stacks.GetStack(provider)
	if !ok {
		m.logger.Printf("[WARN] [integrations] Failed to build context for %s:%s: provider stack not found", provider, target)
		return nil, fmt.Errorf("szolgáltató stack %q nem található", provider)
	}

	// Load decrypted env from provider's app.yaml
	provEnv := m.loadDecryptedEnv(provStack)

	if m.isDebug() {
		envKeyCount := len(provEnv)
		m.logger.Printf("[DEBUG] [integrations] buildApplyContext: provider=%s target=%s domain=%s envKeys=%d", provider, target, m.domain, envKeyCount)
	}

	provMeta := provStack.Meta
	return &ApplyContext{
		ProviderName: provider,
		TargetName:   target,
		Domain:       m.domain,
		ProviderEnv:  provEnv,
		ProviderMeta: &provMeta,
		StacksDir:    m.stacksDir,
		Logger:       m.logger,
		RestartStack: m.stacks.RestartStack,
	}, nil
}

// ReapplyConfigForTarget applies all active integrations targeting the given stack,
// writing config changes only (no container restart). Use this when the caller
// handles container restart separately (e.g. SyncFileBrowserMounts).
func (m *Manager) ReapplyConfigForTarget(targetName string) {
	m.mu.Lock()
	defer m.mu.Unlock()

	all := m.sett.GetIntegrationsForTarget(targetName)
	if m.isDebug() {
		m.logger.Printf("[DEBUG] [integrations] ReapplyConfigForTarget: target=%s integrations=%d", targetName, len(all))
	}
	for key, state := range all {
		if !state.Enabled || state.Status == "disabled" {
			continue
		}

		provider, target, ok := ParseIntegrationKey(key)
		if !ok {
			continue
		}

		handler, hOk := m.handlers[key]
		if !hOk {
			continue
		}

		if m.isDebug() {
			m.logger.Printf("[DEBUG] [integrations] ReapplyConfigForTarget: reapplying %s (status=%s)", key, state.Status)
		}

		ac, err := m.buildApplyContext(provider, target)
		if err != nil {
			m.logger.Printf("[WARN] [integrations] Cannot build context for integration %s reapply: %v", key, err)
			continue
		}

		// Override RestartStack to no-op — caller handles restart
		ac.RestartStack = func(string) error { return nil }

		if err := handler.Apply(ac); err != nil {
			m.logger.Printf("[WARN] [integrations] Integration config reapply failed for %s: %v", key, err)
			if m.isDebug() {
				m.logger.Printf("[DEBUG] [integrations] ReapplyConfigForTarget: %s failed: %v", key, err)
			}
			state.Status = "error"
			state.LastError = err.Error()
			_ = m.sett.SetIntegrationState(key, state)
			continue
		}

		if m.isDebug() {
			m.logger.Printf("[DEBUG] [integrations] ReapplyConfigForTarget: %s succeeded", key)
		}
		state.Status = "active"
		state.LastError = ""
		_ = m.sett.SetIntegrationState(key, state)
		m.logger.Printf("[INFO] [integrations] Integration %s config reapplied for %s", key, targetName)
	}
}

// loadDecryptedEnv reads app.yaml for a stack and decrypts sensitive values.
func (m *Manager) loadDecryptedEnv(s *stacks.Stack) map[string]string {
	stackDir := filepath.Dir(s.ComposePath)
	cfg := stacks.LoadAppConfig(stackDir)
	if cfg == nil {
		m.logger.Printf("[WARN] [integrations] Failed to load env for %s: app config is nil", s.Name)
		return nil
	}
	if m.encKey != nil {
		cfg.Env = crypto.DecryptMap(m.encKey, cfg.Env)
	}
	if m.isDebug() {
		m.logger.Printf("[DEBUG] [integrations] loadDecryptedEnv: stack=%s envKeys=%d", s.Name, len(cfg.Env))
	}
	return cfg.Env
}
