package com.openosrs.client.core;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * LoginState - Manages the login screen state and credentials.
 *
 * Tracks:
 * - Username, password, OTP
 * - Login screen state (e.g., credentials, connecting, logged in)
 * - Login responses and messages
 * - World selection state
 * - Session information
 */
public class LoginState {
    private static final Logger logger = LoggerFactory.getLogger(LoginState.class);
    private static final int MAX_USERNAME_LENGTH = 320;
    private static final int MAX_PASSWORD_LENGTH = 20;
    private static final int MAX_OTP_LENGTH = 6;

    private final EventSystem eventSystem;
    private final ReadWriteLock loginLock = new ReentrantReadWriteLock();

    // Login credentials
    private final AtomicReference<String> username = new AtomicReference<>("");
    private final AtomicReference<String> password = new AtomicReference<>("");
    private final AtomicReference<String> otp = new AtomicReference<>("");

    // Login state
    private final AtomicInteger loginIndex = new AtomicInteger(LoginScreenState.CREDENTIALS);
    private final AtomicInteger currentLoginField = new AtomicInteger(LoginField.USERNAME);

    // Login responses and messages
    private final AtomicReference<String> response0 = new AtomicReference<>("");
    private final AtomicReference<String> response1 = new AtomicReference<>("");
    private final AtomicReference<String> response2 = new AtomicReference<>("");
    private final AtomicReference<String> response3 = new AtomicReference<>("");

    // World selection
    private final AtomicBoolean worldSelectOpen = new AtomicBoolean(false);
    private final AtomicInteger selectedWorldIndex = new AtomicInteger(-1);
    private final AtomicInteger worldSelectPage = new AtomicInteger(0);

    // Session and connection
    private final AtomicInteger sessionId = new AtomicInteger(0);
    private final AtomicReference<String> sessionToken = new AtomicReference<>("");
    private final AtomicLong lastLoginAttempt = new AtomicLong(0);
    private final AtomicInteger loginAttempts = new AtomicInteger(0);

    // Loading state
    private final AtomicInteger loadingPercent = new AtomicInteger(0);
    private final AtomicReference<String> loadingText = new AtomicReference<>("");

    public LoginState(EventSystem eventSystem) {
        this.eventSystem = eventSystem;
    }

    public void initialize() {
        logger.debug("Initializing LoginState");
        reset();
    }

    public void shutdown() {
        logger.debug("Shutting down LoginState");
        clearCredentials();
    }

    public void tick() {
        // Update login state, check timeouts, etc.
        checkLoginTimeout();
    }

    /**
     * Reset login state to initial values.
     */
    public void reset() {
        loginLock.writeLock().lock();
        try {
            setLoginIndex(LoginScreenState.CREDENTIALS);
            setCurrentLoginField(LoginField.USERNAME);
            clearResponses();
            worldSelectOpen.set(false);
            selectedWorldIndex.set(-1);
            worldSelectPage.set(0);
            sessionId.set(0);
            sessionToken.set("");
            lastLoginAttempt.set(0);
            loginAttempts.set(0);
            loadingPercent.set(0);
            loadingText.set("");
        } finally {
            loginLock.writeLock().unlock();
        }
    }

    /**
     * Clear stored credentials for security.
     */
    public void clearCredentials() {
        loginLock.writeLock().lock();
        try {
            username.set("");
            password.set("");
            otp.set("");
            eventSystem.fireEvent(EventSystem.EventType.CREDENTIALS_CLEARED, new LoginEvent(LoginEventType.CREDENTIALS_CLEARED));
        } finally {
            loginLock.writeLock().unlock();
        }
    }

    /**
     * Set username with validation.
     */
    public boolean setUsername(String username) {
        if (username == null) username = "";
        if (username.length() > MAX_USERNAME_LENGTH) {
            logger.warn("Username too long: {} characters", username.length());
            return false;
        }

        loginLock.writeLock().lock();
        try {
            String oldUsername = this.username.getAndSet(username);
            if (!oldUsername.equals(username)) {
                eventSystem.fireEvent(EventSystem.EventType.LOGIN_USERNAME_CHANGED,
                    new LoginEvent(LoginEventType.USERNAME_CHANGED, username));
                logger.debug("Username changed");
            }
            return true;
        } finally {
            loginLock.writeLock().unlock();
        }
    }

    /**
     * Set password with validation.
     */
    public boolean setPassword(String password) {
        if (password == null) password = "";
        if (password.length() > MAX_PASSWORD_LENGTH) {
            logger.warn("Password too long: {} characters", password.length());
            return false;
        }

        loginLock.writeLock().lock();
        try {
            String oldPassword = this.password.getAndSet(password);
            if (!oldPassword.equals(password)) {
                eventSystem.fireEvent(EventSystem.EventType.LOGIN_PASSWORD_CHANGED,
                    new LoginEvent(LoginEventType.PASSWORD_CHANGED));
                logger.debug("Password changed");
            }
            return true;
        } finally {
            loginLock.writeLock().unlock();
        }
    }

    /**
     * Set OTP (One-Time Password) with validation.
     */
    public boolean setOtp(String otp) {
        if (otp == null) otp = "";
        if (otp.length() > MAX_OTP_LENGTH) {
            logger.warn("OTP too long: {} characters", otp.length());
            return false;
        }

        // OTP should be numeric
        if (!otp.isEmpty() && !otp.matches("\\d+")) {
            logger.warn("OTP contains non-numeric characters");
            return false;
        }

        loginLock.writeLock().lock();
        try {
            String oldOtp = this.otp.getAndSet(otp);
            if (!oldOtp.equals(otp)) {
                eventSystem.fireEvent(EventSystem.EventType.LOGIN_OTP_CHANGED,
                    new LoginEvent(LoginEventType.OTP_CHANGED));
                logger.debug("OTP changed");
            }
            return true;
        } finally {
            loginLock.writeLock().unlock();
        }
    }

    /**
     * Set login screen state.
     */
    public void setLoginIndex(int loginIndex) {
        int oldIndex = this.loginIndex.getAndSet(loginIndex);
        if (oldIndex != loginIndex) {
            eventSystem.fireEvent(EventSystem.EventType.LOGIN_STATE_CHANGED,
                new LoginEvent(LoginEventType.STATE_CHANGED, String.valueOf(loginIndex)));
            logger.debug("Login state changed: {} -> {}", oldIndex, loginIndex);
        }
    }

    /**
     * Set current login field focus.
     */
    public void setCurrentLoginField(int field) {
        int oldField = this.currentLoginField.getAndSet(field);
        if (oldField != field) {
            eventSystem.fireEvent(EventSystem.EventType.LOGIN_FIELD_CHANGED,
                new LoginEvent(LoginEventType.FIELD_CHANGED, String.valueOf(field)));
            logger.debug("Login field changed: {} -> {}", oldField, field);
        }
    }

    /**
     * Set login response messages.
     */
    public void setLoginResponse(String response0, String response1, String response2, String response3) {
        loginLock.writeLock().lock();
        try {
            this.response0.set(response0 != null ? response0 : "");
            this.response1.set(response1 != null ? response1 : "");
            this.response2.set(response2 != null ? response2 : "");
            this.response3.set(response3 != null ? response3 : "");

            eventSystem.fireEvent(EventSystem.EventType.LOGIN_RESPONSE_CHANGED,
                new LoginEvent(LoginEventType.RESPONSE_CHANGED, response1));
            logger.debug("Login response updated: {}", response1);
        } finally {
            loginLock.writeLock().unlock();
        }
    }

    /**
     * Clear login response messages.
     */
    public void clearResponses() {
        setLoginResponse("", "", "", "");
    }

    /**
     * Set loading state.
     */
    public void setLoadingState(int percent, String text) {
        loadingPercent.set(Math.max(0, Math.min(100, percent)));
        loadingText.set(text != null ? text : "");

        eventSystem.fireEvent(EventSystem.EventType.LOGIN_LOADING_CHANGED,
            new LoginEvent(LoginEventType.LOADING_CHANGED, text));
    }

    /**
     * Record login attempt.
     */
    public void recordLoginAttempt() {
        lastLoginAttempt.set(System.currentTimeMillis());
        int attempts = loginAttempts.incrementAndGet();
        logger.debug("Login attempt #{}", attempts);
    }

    /**
     * Check for login timeout.
     */
    private void checkLoginTimeout() {
        long lastAttempt = lastLoginAttempt.get();
        if (lastAttempt > 0 && System.currentTimeMillis() - lastAttempt > 30000) { // 30 second timeout
            if (loginIndex.get() == LoginScreenState.CONNECTING) {
                setLoginResponse("", "Connection timed out.", "Please try again.", "");
                setLoginIndex(LoginScreenState.CREDENTIALS);
                logger.warn("Login attempt timed out");
            }
        }
    }

    /**
     * Validate credentials for login attempt.
     */
    public boolean validateCredentials() {
        String user = username.get().trim();
        String pass = password.get();

        if (user.isEmpty()) {
            setLoginResponse("", "Please enter your username/email address.", "", "");
            return false;
        }

        if (pass.isEmpty()) {
            setLoginResponse("", "Please enter your password.", "", "");
            return false;
        }

        return true;
    }

    /**
     * Agent-friendly login method.
     */
    public boolean attemptLogin(String username, String password, String otp) {
        if (!setUsername(username)) return false;
        if (!setPassword(password)) return false;
        if (otp != null && !setOtp(otp)) return false;

        if (!validateCredentials()) return false;

        recordLoginAttempt();
        setLoginIndex(LoginScreenState.CONNECTING);
        setLoginResponse("", "Connecting to server...", "", "");

        eventSystem.fireEvent(EventSystem.EventType.LOGIN_ATTEMPT_STARTED,
            new LoginEvent(LoginEventType.ATTEMPT_STARTED, username));

        return true;
    }

    /**
     * Handle successful login.
     */
    public void onLoginSuccess(int sessionId, String sessionToken) {
        this.sessionId.set(sessionId);
        this.sessionToken.set(sessionToken != null ? sessionToken : "");
        setLoginIndex(LoginScreenState.LOGGED_IN);
        clearResponses();

        eventSystem.fireEvent(EventSystem.EventType.LOGIN_SUCCESS,
            new LoginEvent(LoginEventType.SUCCESS, String.valueOf(sessionId)));
        logger.info("Login successful, session: {}", sessionId);
    }

    /**
     * Handle login failure.
     */
    public void onLoginFailure(int errorCode, String errorMessage) {
        setLoginIndex(LoginScreenState.CREDENTIALS);

        String message = errorMessage != null ? errorMessage : "Login failed";
        setLoginResponse("", message, "", "");

        eventSystem.fireEvent(EventSystem.EventType.LOGIN_FAILED,
            new LoginEvent(LoginEventType.FAILED, String.valueOf(errorCode)));
        logger.warn("Login failed: {} ({})", message, errorCode);
    }

    // Getters
    public String getUsername() { return username.get(); }
    public String getPassword() { return password.get(); }
    public String getOtp() { return otp.get(); }
    public int getLoginIndex() { return loginIndex.get(); }
    public int getCurrentLoginField() { return currentLoginField.get(); }
    public String getResponse0() { return response0.get(); }
    public String getResponse1() { return response1.get(); }
    public String getResponse2() { return response2.get(); }
    public String getResponse3() { return response3.get(); }
    public boolean isWorldSelectOpen() { return worldSelectOpen.get(); }
    public int getSelectedWorldIndex() { return selectedWorldIndex.get(); }
    public int getWorldSelectPage() { return worldSelectPage.get(); }
    public int getSessionId() { return sessionId.get(); }
    public String getSessionToken() { return sessionToken.get(); }
    public int getLoadingPercent() { return loadingPercent.get(); }
    public String getLoadingText() { return loadingText.get(); }
    public int getLoginAttempts() { return loginAttempts.get(); }
    public long getLastLoginAttempt() { return lastLoginAttempt.get(); }

    public boolean isLoggedIn() {
        return loginIndex.get() == LoginScreenState.LOGGED_IN && sessionId.get() > 0;
    }

    public boolean isConnecting() {
        return loginIndex.get() == LoginScreenState.CONNECTING;
    }

    /**
     * Login screen states (based on RuneLite's loginIndex).
     */
    public static class LoginScreenState {
        public static final int STARTUP = 0;
        public static final int CREDENTIALS = 2;
        public static final int AUTHENTICATOR = 4;
        public static final int CONNECTING = 5;
        public static final int LOGGED_IN = 10;
        public static final int WORLD_SELECT = 7;
        public static final int ERROR = -1;
    }

    /**
     * Login field focus constants.
     */
    public static class LoginField {
        public static final int USERNAME = 0;
        public static final int PASSWORD = 1;
        public static final int OTP = 2;
    }

    /**
     * Login event types.
     */
    public enum LoginEventType {
        CREDENTIALS_CLEARED,
        USERNAME_CHANGED,
        PASSWORD_CHANGED,
        OTP_CHANGED,
        STATE_CHANGED,
        FIELD_CHANGED,
        RESPONSE_CHANGED,
        LOADING_CHANGED,
        ATTEMPT_STARTED,
        SUCCESS,
        FAILED
    }

    /**
     * Login event class.
     */
    public static class LoginEvent extends EventSystem.GameEvent {
        private final LoginEventType loginEventType;
        private final String data;

        public LoginEvent(LoginEventType type) {
            this(type, null);
        }

        public LoginEvent(LoginEventType type, String data) {
            super(EventSystem.EventType.LOGIN_EVENT);
            this.loginEventType = type;
            this.data = data;
        }

        public LoginEventType getLoginEventType() { return loginEventType; }
        public String getData() { return data; }
    }
}
