package com.openosrs.client.api;

import com.openosrs.client.core.ClientCore;
import com.openosrs.client.core.LoginState;
import com.openosrs.client.core.EventSystem;
import com.openosrs.client.core.PlayerState;
import com.openosrs.client.core.InventoryState;
import com.openosrs.client.core.bridge.BridgeAdapter;
import com.openosrs.client.api.types.Position;
import com.openosrs.client.api.types.Skill;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

/**
 * AgentAPI - High-level API for AI agents to interact with the OpenOSRS client.
 * This API provides convenient methods for login, game actions, and state monitoring
 * specifically designed for programmatic agent usage.
 */
public class AgentAPI {
    private static final Logger logger = LoggerFactory.getLogger(AgentAPI.class);

    private final ClientCore clientCore;
    private final AtomicBoolean initialized = new AtomicBoolean(false);

    // API modules
    private final PlayerAPI playerAPI;
    private final WorldAPI worldAPI;
    private final InventoryAPI inventoryAPI;
    private final CombatAPI combatAPI;
    private final NavigationAPI navigationAPI;
    private final InteractionAPI interactionAPI;

    // Login callbacks
    private volatile Consumer<LoginResult> loginSuccessCallback = null;
    private volatile Consumer<LoginError> loginFailureCallback = null;
    private volatile Consumer<String> loginProgressCallback = null;

    // Auto-reconnection settings
    private volatile boolean autoReconnectEnabled = false;
    private volatile int autoReconnectDelaySeconds = 30;
    private volatile int maxReconnectAttempts = 5;
    private volatile int currentReconnectAttempts = 0;

    public AgentAPI(ClientCore clientCore) {
        this.clientCore = clientCore;

        // Initialize API modules with bridge adapter
        BridgeAdapter bridgeAdapter = clientCore.getBridgeAdapter();
        this.playerAPI = new PlayerAPI(clientCore, bridgeAdapter);
        this.worldAPI = new WorldAPI(clientCore, bridgeAdapter);
        this.inventoryAPI = new InventoryAPI(clientCore, bridgeAdapter);
        this.combatAPI = new CombatAPI(clientCore, bridgeAdapter);
        this.navigationAPI = new NavigationAPI(clientCore, null); // GameEngine not available yet
        this.interactionAPI = new InteractionAPI(clientCore, null); // GameEngine not available yet
    }

    /**
     * Initialize the AgentAPI with the client core.
     */
    public void initialize() {
        if (initialized.compareAndSet(false, true)) {
            logger.info("AgentAPI initialized");
            setupEventListeners();
        }
    }

    /**
     * Shutdown the AgentAPI.
     */
    public void shutdown() {
        if (initialized.compareAndSet(true, false)) {
            logger.info("AgentAPI shutdown");
            autoReconnectEnabled = false;
        }
    }

    // ========== API MODULE ACCESSORS ==========

    /**
     * Get the PlayerAPI for player-specific queries.
     */
    public PlayerAPI getPlayerAPI() {
        return playerAPI;
    }

    /**
     * Get the WorldAPI for world object queries.
     */
    public WorldAPI getWorldAPI() {
        return worldAPI;
    }

    /**
     * Get the InventoryAPI for inventory management.
     */
    public InventoryAPI getInventoryAPI() {
        return inventoryAPI;
    }

    /**
     * Get the CombatAPI for combat actions.
     */
    public CombatAPI getCombatAPI() {
        return combatAPI;
    }

    /**
     * Get the NavigationAPI for pathfinding.
     */
    public NavigationAPI getNavigationAPI() {
        return navigationAPI;
    }

    /**
     * Get the InteractionAPI for game interactions.
     */
    public InteractionAPI getInteractionAPI() {
        return interactionAPI;
    }

    // ========== LOGIN METHODS ==========

    /**
     * Login with username and password (blocking).
     *
     * @param username The username
     * @param password The password
     * @return LoginResult containing success/failure information
     */
    public LoginResult login(String username, String password) {
        return login(username, password, null, 30);
    }

    /**
     * Login with username, password, and OTP (blocking).
     *
     * @param username The username
     * @param password The password
     * @param otp The one-time password (optional)
     * @return LoginResult containing success/failure information
     */
    public LoginResult login(String username, String password, String otp) {
        return login(username, password, otp, 30);
    }

    /**
     * Login with username, password, OTP, and custom timeout (blocking).
     *
     * @param username The username
     * @param password The password
     * @param otp The one-time password (optional)
     * @param timeoutSeconds Timeout in seconds
     * @return LoginResult containing success/failure information
     */
    public LoginResult login(String username, String password, String otp, int timeoutSeconds) {
        try {
            return loginAsync(username, password, otp).get(timeoutSeconds, TimeUnit.SECONDS);
        } catch (Exception e) {
            logger.error("Login failed with exception", e);
            return new LoginResult(false, "Login timeout or error: " + e.getMessage(), -1, null);
        }
    }

    /**
     * Login asynchronously.
     *
     * @param username The username
     * @param password The password
     * @param otp The one-time password (optional)
     * @return CompletableFuture<LoginResult>
     */
    public CompletableFuture<LoginResult> loginAsync(String username, String password, String otp) {
        CompletableFuture<LoginResult> future = new CompletableFuture<>();

        if (!initialized.get()) {
            future.complete(new LoginResult(false, "AgentAPI not initialized", -1, null));
            return future;
        }

        if (isLoggedIn()) {
            future.complete(new LoginResult(false, "Already logged in", -1, null));
            return future;
        }

        LoginState loginState = clientCore.getLoginState();

        // Set up one-time listeners for this login attempt
        Consumer<EventSystem.Event> successListener = new Consumer<EventSystem.Event>() {
            @Override
            public void accept(EventSystem.Event event) {
                if (event instanceof LoginState.LoginEvent) {
                    LoginState.LoginEvent loginEvent = (LoginState.LoginEvent) event;
                    future.complete(new LoginResult(true, "Login successful",
                        loginEvent.getSessionId(), loginEvent.getSessionToken()));
                    clientCore.getEventSystem().removeListener(EventSystem.EventType.LOGIN_SUCCESS, this);
                }
            }
        };

        Consumer<EventSystem.Event> failureListener = new Consumer<EventSystem.Event>() {
            @Override
            public void accept(EventSystem.Event event) {
                if (event instanceof LoginState.LoginEvent) {
                    LoginState.LoginEvent loginEvent = (LoginState.LoginEvent) event;
                    future.complete(new LoginResult(false, loginEvent.getErrorMessage(),
                        loginEvent.getErrorCode(), null));
                    clientCore.getEventSystem().removeListener(EventSystem.EventType.LOGIN_FAILED, this);
                }
            }
        };

        clientCore.getEventSystem().addListener(EventSystem.EventType.LOGIN_SUCCESS, successListener);
        clientCore.getEventSystem().addListener(EventSystem.EventType.LOGIN_FAILED, failureListener);

        // Start login attempt
        try {
            loginState.setCredentials(username, password, otp);
            loginState.attemptLogin();
        } catch (Exception e) {
            future.complete(new LoginResult(false, "Failed to start login: " + e.getMessage(), -1, null));
        }

        return future;
    }

    /**
     * Set login callbacks for asynchronous login handling.
     *
     * @param onSuccess Called when login succeeds
     * @param onFailure Called when login fails
     * @param onProgress Called during login progress (optional)
     */
    public void setLoginCallbacks(Consumer<LoginResult> onSuccess,
                                  Consumer<LoginError> onFailure,
                                  Consumer<String> onProgress) {
        this.loginSuccessCallback = onSuccess;
        this.loginFailureCallback = onFailure;
        this.loginProgressCallback = onProgress;
    }

    /**
     * Enable or disable automatic reconnection.
     *
     * @param enabled Whether to enable auto-reconnection
     * @param delaySeconds Delay between reconnection attempts
     * @param maxAttempts Maximum number of reconnection attempts
     */
    public void setAutoReconnect(boolean enabled, int delaySeconds, int maxAttempts) {
        this.autoReconnectEnabled = enabled;
        this.autoReconnectDelaySeconds = delaySeconds;
        this.maxReconnectAttempts = maxAttempts;

        if (enabled) {
            logger.info("Auto-reconnection enabled: delay={}s, maxAttempts={}", delaySeconds, maxAttempts);
        } else {
            logger.info("Auto-reconnection disabled");
        }
    }

    /**
     * Logout from the game.
     */
    public void logout() {
        if (isLoggedIn()) {
            clientCore.getLoginState().logout();
            logger.info("Logout initiated");
        }
    }

    /**
     * Check if currently logged in.
     */
    public boolean isLoggedIn() {
        return clientCore.getLoginState().getState() == LoginState.State.LOGGED_IN;
    }

    /**
     * Check if login is in progress.
     */
    public boolean isLoginInProgress() {
        return clientCore.getLoginState().getState() == LoginState.State.LOGGING_IN;
    }

    /**
     * Get current login state.
     */
    public LoginState.State getLoginState() {
        return clientCore.getLoginState().getState();
    }

    // ========== CONVENIENCE METHODS ==========

    /**
     * Get current player position (convenience method).
     */
    public Position getPlayerPosition() {
        return playerAPI.getPosition();
    }

    /**
     * Get current player health (convenience method).
     */
    public int getPlayerHealth() {
        return playerAPI.getHitpoints();
    }

    /**
     * Get current player energy (convenience method).
     */
    public int getPlayerEnergy() {
        return playerAPI.getRunEnergy();
    }

    /**
     * Check if player is moving (convenience method).
     */
    public boolean isPlayerMoving() {
        return playerAPI.isMoving();
    }

    /**
     * Get inventory item count (convenience method).
     */
    public int getInventoryItemCount(int itemId) {
        return inventoryAPI.getItemCount(itemId);
    }

    /**
     * Check if inventory contains item (convenience method).
     */
    public boolean hasInventoryItem(int itemId) {
        return inventoryAPI.hasItem(itemId);
    }

    // ========== EVENT MONITORING ==========

    /**
     * Add a listener for specific event types.
     */
    public void addEventListener(EventSystem.EventType eventType, Consumer<EventSystem.Event> listener) {
        clientCore.getEventSystem().addListener(eventType, listener);
    }

    /**
     * Remove an event listener.
     */
    public void removeEventListener(EventSystem.EventType eventType, Consumer<EventSystem.Event> listener) {
        clientCore.getEventSystem().removeListener(eventType, listener);
    }

    // ========== HELPER CLASSES ==========

    /**
     * Result of a login attempt.
     */
    public static class LoginResult {
        private final boolean success;
        private final String message;
        private final int sessionId;
        private final String sessionToken;

        public LoginResult(boolean success, String message, int sessionId, String sessionToken) {
            this.success = success;
            this.message = message;
            this.sessionId = sessionId;
            this.sessionToken = sessionToken;
        }

        public boolean isSuccess() { return success; }
        public String getMessage() { return message; }
        public int getSessionId() { return sessionId; }
        public String getSessionToken() { return sessionToken; }

        @Override
        public String toString() {
            return "LoginResult{success=" + success + ", message='" + message + "', sessionId=" + sessionId + "}";
        }
    }

    /**
     * Login error information.
     */
    public static class LoginError {
        private final int errorCode;
        private final String errorMessage;

        public LoginError(int errorCode, String errorMessage) {
            this.errorCode = errorCode;
            this.errorMessage = errorMessage;
        }

        public int getErrorCode() { return errorCode; }
        public String getErrorMessage() { return errorMessage; }

        @Override
        public String toString() {
            return "LoginError{code=" + errorCode + ", message='" + errorMessage + "'}";
        }
    }

    // ========== PRIVATE METHODS ==========

    private void setupEventListeners() {
        // Listen for login events to trigger callbacks
        clientCore.getEventSystem().addListener(EventSystem.EventType.LOGIN_SUCCESS, event -> {
            if (loginSuccessCallback != null && event instanceof LoginState.LoginEvent) {
                LoginState.LoginEvent loginEvent = (LoginState.LoginEvent) event;
                LoginResult result = new LoginResult(true, "Login successful",
                    loginEvent.getSessionId(), loginEvent.getSessionToken());
                loginSuccessCallback.accept(result);
            }
            currentReconnectAttempts = 0; // Reset reconnect counter on successful login
        });

        clientCore.getEventSystem().addListener(EventSystem.EventType.LOGIN_FAILED, event -> {
            if (loginFailureCallback != null && event instanceof LoginState.LoginEvent) {
                LoginState.LoginEvent loginEvent = (LoginState.LoginEvent) event;
                LoginError error = new LoginError(loginEvent.getErrorCode(), loginEvent.getErrorMessage());
                loginFailureCallback.accept(error);
            }
        });

        clientCore.getEventSystem().addListener(EventSystem.EventType.LOGIN_PROGRESS, event -> {
            if (loginProgressCallback != null && event instanceof LoginState.LoginEvent) {
                LoginState.LoginEvent loginEvent = (LoginState.LoginEvent) event;
                loginProgressCallback.accept(loginEvent.getProgressMessage());
            }
        });

        // Listen for disconnection events for auto-reconnection
        clientCore.getEventSystem().addListener(EventSystem.EventType.DISCONNECTED, event -> {
            if (autoReconnectEnabled && currentReconnectAttempts < maxReconnectAttempts) {
                handleAutoReconnect();
            }
        });
    }

    private void handleAutoReconnect() {
        currentReconnectAttempts++;
        logger.info("Auto-reconnection attempt {} of {}", currentReconnectAttempts, maxReconnectAttempts);

        // Delay reconnection attempt
        new Thread(() -> {
            try {
                Thread.sleep(autoReconnectDelaySeconds * 1000);

                LoginState loginState = clientCore.getLoginState();
                String lastUsername = loginState.getUsername();
                String lastPassword = loginState.getPassword();
                String lastOtp = loginState.getOtp();

                if (!lastUsername.isEmpty() && !lastPassword.isEmpty()) {
                    logger.info("Attempting auto-reconnection for user: {}", lastUsername);
                    loginAsync(lastUsername, lastPassword, lastOtp);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }).start();
    }
}
