package org.distributed;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
import javax.jms.*;
import org.apache.activemq.ActiveMQConnectionFactory;

public class ChatHandler {

    private String userId, corespondent;
    private Connection connection;
    private Session senderSession, receiverSession;
    private final Map<String, MessageProducer> blocked = new ConcurrentHashMap<>();
    private final String TAG = "Task3_queue_";
    private final String BROKER_URL = "tcp://localhost:61616";
    /**
     * Initialize JMS session
     * @throws JMSException
     */
    public void initializeSession() throws JMSException {
        ConnectionFactory factory = new ActiveMQConnectionFactory(BROKER_URL);
        this.connection = factory.createConnection();
        this.connection.start();
        this.senderSession = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        this.receiverSession = this.connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    }

    /**
     * Start conversation loop
     * @param sc
     * @throws JMSException
     */
    public void startConversation(Scanner sc) throws JMSException {

        // init receiving thread
        Thread t = setupReceiver(userId);

        // Continuous input loop
        try {
            while (sc.hasNextLine()){

                // handle user input and send messages
                // or if exit command, break loop
                if(!respond(sc)){ 
                    break;
                }
            }
        } catch (Exception e) {
            System.err.println("Error in continuous conversation: " + e.getMessage());
        } finally {
            t.interrupt();
        }
    }

    /**
     * Handle user input and send messages
     * @param sc
     * @throws JMSException
     */
    private boolean respond(Scanner sc) throws JMSException {
        String line = sc.nextLine();

        // commands to block or unblock contact
        if(line.startsWith("-")){
            delegateBlockingManagement(sc,line);
            // handling edge case where instead of user, exit comand is given
            return !corespondent.equalsIgnoreCase("exit");
        }

        if (line.equalsIgnoreCase("exit")) {
            System.out.println("Exiting conversation...");
            return false;
        }

        // Send message to receiver
        sendMessage(createProducer(corespondent), line);
        return true;
    }


    /**
     * Delegate blocking and unblocking management
     * @param sc
     * @param line
     */
    private void delegateBlockingManagement(Scanner sc, String line) {

        // block command is -b followed by contact id
        if(line.startsWith("-b")){
            try {
                boolean wasCorespondent = blockContact(line);
                // if we blocked current correspondent, prompt for new one
                if(wasCorespondent){
                    System.out.println("You have blocked your current correspondent. Please choose a new one.");
                    promptForCorespondent(sc);
                }
            } catch (JMSException e) {
                System.err.println(e.getMessage());
            }
            return;
        }

        // unblock command is -u followed by contact id
        if(line.startsWith("-u")){
            String contact = unBlockContact(line);
            System.out.println("do you want to chat with: " + contact+" yes or no");
            while(true){
                String answer = sc.nextLine();
                if(answer.equalsIgnoreCase("yes")){
                    setCorespondent(contact);
                    break;
                } else if(answer.equalsIgnoreCase("no")){
                    promptForCorespondent(sc);
                    break;
                } else {
                    System.out.println("please answer with yes or no");
                }
            }
        }
    }

    // Setup producer and consumer for a receiver
    private Thread setupReceiver(String receiver) throws JMSException {
        MessageConsumer contact = createConsumer();

        // add consumer to contacts map
        return getThread(contact);
    }

    /**
     * Create a producer for a given contact ID so we can send messages
     * @param id
     * @return MessageProducer
     * @throws JMSException
     */
    private MessageProducer createProducer(String id) throws JMSException {
        String from = TAG + id;
        Queue prod = senderSession.createQueue(from);
        return senderSession.createProducer(prod);
    }

    /**
     * Block a contact
     * @param command
     */
    private boolean blockContact(String command) throws JMSException {
        // remove the command part to get the contact id
        String contact = command.replace("-b", "").trim();
        if(contact.isEmpty()){
            throw new JMSException("No contact specified to block.");
        }
        
        boolean isCurrentCorespondent = contact.equals(getCorespondent());
        MessageProducer ignored = createProducer(contact);

        // check if already blocked
        if(blocked.containsKey(contact)){
            System.out.println(contact + " is already blocked.");
            return isCurrentCorespondent;
        }
        // remove from contacts map
        // and close his ass
        if(ignored != null){
            ignored.close();
            blocked.put(contact, ignored);
            System.out.println(contact + " has been blocked.");
            return isCurrentCorespondent;
        }else{
            throw new JMSException("Could not block contact: " + contact);
        }   
        
    }


    /**
     * Unblock a contact
     * @param command
     * @return contact id
     */
    private String unBlockContact(String command) {

        String contact = command.replace("-u", "").trim();

        
        try {
            // unblock by removing his ass from blocklist
            blocked.remove(contact);
            // retun contact name for further handling
            return contact;

        } catch (Exception e) {
            System.err.println("Error blocking consumer: " + e.getMessage());
            return null;
        }
    }

    /**
     * Subscribe to topics meant for this user
     * @return MessageConsumer
     * @throws JMSException
     */
    private MessageConsumer createConsumer() throws JMSException {
        String to = TAG + userId;
        Queue cons = receiverSession.createQueue(to);
        return receiverSession.createConsumer(cons);
    }

    /**
     * Send a messag
     * @param producer
     * @param line
     * @throws JMSException
     */
    private void sendMessage(MessageProducer producer, String line) throws JMSException {

        TextMessage m = senderSession.createTextMessage(line);
        // add sender identity to message
        m.setStringProperty("sender", userId);
        producer.send(m);
    }

    /**
     * Close session and connection
     * @throws JMSException
     */
    public void closeSession() throws JMSException {
        if (senderSession != null) {
            senderSession.close();
        }
        if( receiverSession != null) {
            receiverSession.close();
        }
        if (connection != null) {
            connection.close();
        }
    }

    /**
     * Returns a thread object to handle receiving messages
     * checks if sender is blocked before displaying
     * @param consumer
     * @return Thread
     */
    private Thread getThread(MessageConsumer consumer) {

        Thread receivingThread = new Thread(() -> {
            try {

                while (!Thread.currentThread().isInterrupted()) {

                    // receive message with timeout
                    Message msg = consumer.receive(100); // 100ms timeout
                    if (msg instanceof TextMessage tm) {

                        // message contents
                        String sender = tm.getStringProperty("sender");
                        String body = tm.getText();

                        // display conditions
                        boolean isBlocked = blocked.containsKey(sender);
                        boolean isEmptyMessage = body.isBlank() || body.isEmpty();

                        // display only if not blocked and not empty
                        if (!isBlocked && !isEmptyMessage) {
                            System.out.println("[" + sender + "]: " + body);
                        }
                        
                    }
                }
            } catch (javax.jms.JMSException e) {
                // Silently handle interruption - normal shutdown behavior
                if (!Thread.currentThread().isInterrupted()) {
                    System.err.println("Error receiving message: " + e.getMessage());
                }
            }
        });

        
        receivingThread.start();
        // we are returning this so we can interrupt it later
        return receivingThread;
    }


    /**
     * Prompt for user ID
     * @param sc
     */
    public void promptForUser(Scanner sc){

        System.out.print("Enter your user ID: ");
        setUserId(prompt(sc));
        // Instructions
        System.out.print("Welcome "+getUserId());
        printCommands();
    }

    /**
     * Print available commands
     */
    private void printCommands() {
        System.out.println("\nYou can use the following commands during the chat:");
        System.out.println("- To block a contact: -b <contact_id>");
        System.out.println("- To unblock a contact: -u <contact_id>");
        System.out.println("- To exit the chat: exit");
    }   

    /**
     * Prompt for correspondent ID
     * @param sc
     */
    public void promptForCorespondent(Scanner sc){

        System.out.print("Who do you want to chat with? ");
        String contact = prompt(sc);
        setCorespondent(contact);
        // check if contact is blocked
        if(blocked.containsKey(contact)){
            System.out.println(contact + " is currently blocked. Unblock to chat.");
            return;
        }
        System.out.println("You can start chatting with: " + getCorespondent());
    }

    /**
     * Generic prompt method prompts for whatever we need
     * @param sc Scanner object
     * @return String
     */
    private String prompt(Scanner sc) {

        String user = "";

        while (user.isEmpty()) {
            try {
                user = sc.next();

            } catch (Exception e) {
                System.err.println("Error reading correspondent ID: " + e.getMessage());
                return null;
            }
        }
        return user;
    }

    // Setters and getters
    // user
    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUserId() {
        return userId;
    }

    // correspondent
    public String getCorespondent() {
        return corespondent;
    }

    public void setCorespondent(String corespondent) {
        this.corespondent = corespondent;
    }
}
