package com.openosrs.client.login;

import com.openosrs.client.core.ClientCore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;

/**
 * LoginManager - Core component for automated login to RuneScape.
 * 
 * This class handles the complete login process including:
 * - World selection and connection
 * - Authentication with username/password
 * - Login state monitoring and validation
 * - Error handling and retry logic
 * 
 * Designed specifically for AI agents to reliably connect to the game.
 */
public class LoginManager {
    private static final Logger logger = LoggerFactory.getLogger(LoginManager.class);
    
    // Login timeouts in seconds
    private static final int DEFAULT_CONNECTION_TIMEOUT = 30;
    private static final int DEFAULT_LOGIN_TIMEOUT = 60;
    private static final int RETRY_DELAY_MS = 2000;
    private static final int MAX_RETRY_ATTEMPTS = 3;
    
    private final ClientCore clientCore;
    private final LoginCredentials credentials;
    private final GameConnectionManager connectionManager;
    private final LoginStateTracker stateTracker;
    
    private volatile LoginState currentState = LoginState.DISCONNECTED;
    private volatile String lastError = null;
    private Consumer<LoginState> stateChangeCallback;
    
    /**
     * Create a new LoginManager with the specified core components.
     */
    public LoginManager(ClientCore clientCore) {
        this.clientCore = clientCore;
        this.credentials = new LoginCredentials();
        this.connectionManager = new GameConnectionManager(clientCore);
        this.stateTracker = new LoginStateTracker();
        
        // Set up state tracking
        this.stateTracker.setStateChangeListener(this::onStateChanged);
        
        logger.info("LoginManager initialized");
    }
    
    /**
     * Set credentials for login authentication.
     * 
     * @param username Account username or email
     * @param password Account password
     */
    public void setCredentials(String username, String password) {
        credentials.setCredentials(username, password);
        logger.info("Login credentials set for user: {}", username.replaceAll(".", "*"));
    }
    
    /**
     * Load credentials from encrypted storage.
     * 
     * @param credentialsFile Path to encrypted credentials file
     * @return true if credentials loaded successfully
     */
    public boolean loadCredentials(String credentialsFile) {
        try {
            boolean loaded = credentials.loadFromFile(credentialsFile);
            if (loaded) {
                logger.info("Credentials loaded from file: {}", credentialsFile);
            } else {
                logger.warn("Failed to load credentials from file: {}", credentialsFile);
            }
            return loaded;
        } catch (Exception e) {
            logger.error("Error loading credentials", e);
            return false;
        }
    }
    
    /**
     * Set a callback to be notified when login state changes.
     */
    public void setStateChangeCallback(Consumer<LoginState> callback) {
        this.stateChangeCallback = callback;
    }
    
    /**
     * Start the automated login process.
     * 
     * This method will:
     * 1. Connect to RuneScape servers
     * 2. Select an appropriate world
     * 3. Authenticate with the provided credentials
     * 4. Validate successful login
     * 
     * @return CompletableFuture that completes when login is successful
     */
    public CompletableFuture<Boolean> login() {
        return login(DEFAULT_LOGIN_TIMEOUT);
    }
    
    /**
     * Start the automated login process with custom timeout.
     * 
     * @param timeoutSeconds Maximum time to wait for login completion
     * @return CompletableFuture that completes when login is successful
     */
    public CompletableFuture<Boolean> login(int timeoutSeconds) {
        if (!credentials.isValid()) {
            lastError = "No valid credentials provided";
            logger.error(lastError);
            return CompletableFuture.completedFuture(false);
        }
        
        logger.info("Starting automated login process...");
        currentState = LoginState.CONNECTING;
        
        return CompletableFuture.supplyAsync(() -> {
            try {
                return performLoginWithRetry(timeoutSeconds);
            } catch (Exception e) {
                lastError = "Login failed: " + e.getMessage();
                logger.error(lastError, e);
                currentState = LoginState.FAILED;
                return false;
            }
        }).orTimeout(timeoutSeconds, TimeUnit.SECONDS)
          .exceptionally(throwable -> {
              if (throwable instanceof TimeoutException) {
                  lastError = "Login timed out after " + timeoutSeconds + " seconds";
              } else {
                  lastError = "Login failed: " + throwable.getMessage();
              }
              logger.error(lastError, throwable);
              currentState = LoginState.FAILED;
              return false;
          });
    }
    
    /**
     * Attempt login with retry logic.
     */
    private boolean performLoginWithRetry(int timeoutSeconds) throws Exception {
        int attempts = 0;
        Exception lastException = null;
        
        while (attempts < MAX_RETRY_ATTEMPTS) {
            attempts++;
            logger.info("Login attempt {} of {}", attempts, MAX_RETRY_ATTEMPTS);
            
            try {
                if (performSingleLoginAttempt(timeoutSeconds)) {
                    return true;
                }
            } catch (Exception e) {
                lastException = e;
                logger.warn("Login attempt {} failed: {}", attempts, e.getMessage());
                
                if (attempts < MAX_RETRY_ATTEMPTS) {
                    logger.info("Retrying in {} ms...", RETRY_DELAY_MS);
                    Thread.sleep(RETRY_DELAY_MS);
                }
            }
        }
        
        // All attempts failed
        if (lastException != null) {
            throw lastException;
        } else {
            throw new Exception("All login attempts failed");
        }
    }
    
    /**
     * Perform a single login attempt.
     */
    private boolean performSingleLoginAttempt(int timeoutSeconds) throws Exception {
        // Phase 1: Connect to game servers
        currentState = LoginState.CONNECTING;
        logger.info("Phase 1: Connecting to RuneScape servers...");
        
        if (!connectionManager.connect(DEFAULT_CONNECTION_TIMEOUT)) {
            throw new Exception("Failed to connect to game servers");
        }
        
        // Phase 2: Select world
        currentState = LoginState.SELECTING_WORLD;
        logger.info("Phase 2: Selecting optimal world...");
        
        int selectedWorld = connectionManager.selectOptimalWorld();
        if (selectedWorld <= 0) {
            throw new Exception("Failed to select a suitable world");
        }
        logger.info("Selected world: {}", selectedWorld);
        
        // Phase 3: Authenticate
        currentState = LoginState.AUTHENTICATING;
        logger.info("Phase 3: Authenticating with credentials...");
        
        if (!connectionManager.authenticate(credentials)) {
            throw new Exception("Authentication failed - check credentials");
        }
        
        // Phase 4: Validate login success
        currentState = LoginState.VALIDATING;
        logger.info("Phase 4: Validating successful login...");
        
        if (!validateLoginSuccess(10)) { // 10 second validation timeout
            throw new Exception("Login validation failed - may not be fully connected");
        }
        
        // Login successful!
        currentState = LoginState.LOGGED_IN;
        logger.info("Login completed successfully!");
        return true;
    }
    
    /**
     * Validate that login was successful by checking game state.
     */
    private boolean validateLoginSuccess(int timeoutSeconds) {
        long startTime = System.currentTimeMillis();
        long timeoutMs = timeoutSeconds * 1000L;
        
        while (System.currentTimeMillis() - startTime < timeoutMs) {
            try {
                // Check if we're in-game by testing basic game state
                if (isInGame()) {
                    logger.info("Login validation successful - player is in-game");
                    return true;
                }
                
                Thread.sleep(500); // Check every 500ms
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        
        logger.warn("Login validation timed out - unable to confirm in-game state");
        return false;
    }
    
    /**
     * Check if the player is successfully logged into the game.
     */
    private boolean isInGame() {
        try {
            // Use the client core to check if we have a valid game state
            return clientCore.isInitialized() && 
                   clientCore.getPlayerState().isValidPlayer();
        } catch (Exception e) {
            logger.debug("Error checking in-game state: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * Disconnect from the game and clean up.
     */
    public CompletableFuture<Void> logout() {
        logger.info("Initiating logout...");
        currentState = LoginState.DISCONNECTING;
        
        return CompletableFuture.runAsync(() -> {
            try {
                connectionManager.disconnect();
                currentState = LoginState.DISCONNECTED;
                logger.info("Logout completed");
            } catch (Exception e) {
                logger.error("Error during logout", e);
                currentState = LoginState.FAILED;
            }
        });
    }
    
    /**
     * Get the current login state.
     */
    public LoginState getCurrentState() {
        return currentState;
    }
    
    /**
     * Check if currently logged into the game.
     */
    public boolean isLoggedIn() {
        return currentState == LoginState.LOGGED_IN && isInGame();
    }
    
    /**
     * Get the last error message if login failed.
     */
    public String getLastError() {
        return lastError;
    }
    
    /**
     * Handle state changes from the state tracker.
     */
    private void onStateChanged(LoginState newState) {
        this.currentState = newState;
        
        if (stateChangeCallback != null) {
            try {
                stateChangeCallback.accept(newState);
            } catch (Exception e) {
                logger.warn("Error in state change callback", e);
            }
        }
        
        logger.debug("Login state changed to: {}", newState);
    }
    
    /**
     * Get connection information and statistics.
     */
    public LoginStatus getStatus() {
        return new LoginStatus(
            currentState,
            connectionManager.getCurrentWorld(),
            connectionManager.getPing(),
            isLoggedIn(),
            lastError
        );
    }
    
    /**
     * Enable or disable auto-reconnect on disconnection.
     */
    public void setAutoReconnect(boolean autoReconnect) {
        connectionManager.setAutoReconnect(autoReconnect);
        logger.info("Auto-reconnect {}", autoReconnect ? "enabled" : "disabled");
    }
    
    /**
     * Simple container for login status information.
     */
    public static class LoginStatus {
        private final LoginState state;
        private final int currentWorld;
        private final int ping;
        private final boolean inGame;
        private final String lastError;
        
        public LoginStatus(LoginState state, int currentWorld, int ping, boolean inGame, String lastError) {
            this.state = state;
            this.currentWorld = currentWorld;
            this.ping = ping;
            this.inGame = inGame;
            this.lastError = lastError;
        }
        
        public LoginState getState() { return state; }
        public int getCurrentWorld() { return currentWorld; }
        public int getPing() { return ping; }
        public boolean isInGame() { return inGame; }
        public String getLastError() { return lastError; }
        
        @Override
        public String toString() {
            return String.format("LoginStatus{state=%s, world=%d, ping=%dms, inGame=%s, error='%s'}", 
                state, currentWorld, ping, inGame, lastError);
        }
    }
}