import os
import sys
import json
import re
import random

# === Step 1: Check if `requests` is installed ===
try:
    import requests
except ImportError:
    print("\n⚠️ `requests` module not found! Checking internet connection...\n")
    def check_connection():
        try:
            os.system("ping -c 1 bing.com >nul 2>&1")  # Linux/macOS
            os.system("ping -n 1 bing.com >nul 2>&1")   # Windows
            return True
        except Exception:
            return False
    online = check_connection()
    if online:
        print("\n🌐 Internet detected! Installing `requests`...\n")
        os.system("pip install requests")
        try:
            import requests
            print("\n✅ `requests` installed successfully!\n")
        except ImportError:
            print("\n🚨 Installation failed! Please install manually: `pip install requests`\n")
            sys.exit(1)
    else:
        print("\n🚨 No internet detected! Running in offline mode...\n")
        class FakeRequests:
            def get(self, url, timeout=5):
                return None
        requests = FakeRequests()

# === Step 2: Check if nltk is installed ===
try:
    import nltk
    from nltk.corpus import stopwords
    from nltk.tokenize import word_tokenize
except ImportError:
    print("\n⚠️ `nltk` module not found! Please install it using: pip install nltk")
    sys.exit(1)

# Download required NLTK data if not already present.
try:
    nltk.data.find('tokenizers/punkt')
except LookupError:
    nltk.download('punkt')
try:
    nltk.data.find('corpora/stopwords')
except LookupError:
    nltk.download('stopwords')

# === NLP Processor ===
class NLPProcessor:
    def __init__(self):
        # Minimal stopwords list, extended with some Chinese words
        self.stop_words = set(stopwords.words('english')).union({"我", "的", "了", "在", "是"})
    
    def clean_text(self, text):
        """Tokenizes text, removes non-alphanumeric tokens and stopwords, and returns cleaned string."""
        tokens = word_tokenize(text.lower())
        filtered = [word for word in tokens if word.isalnum() and word not in self.stop_words]
        return " ".join(filtered)

# === Thinking Chain System ===
class ThinkingChain:
    def reason_step_by_step(self, question):
        """Provides step-by-step reasoning for complex questions."""
        q = question.lower()
        if q.startswith("why"):
            return ("Let's break down the causes step by step: first, consider the underlying factors; "
                    "next, see how these factors interact; then, we reach the conclusion.")
        elif q.startswith("how") and "are you" not in q:
            return ("Let's analyze it in stages: first, identify the main components; then, examine how they work together; "
                    "finally, synthesize the overall process.")
        else:
            return ""
    
    def suggest_followup(self, answer):
        """Suggests a follow-up question to deepen the conversation."""
        return "Does that answer your question? Would you like to discuss this further?"

# === Analyzer System ===
class Analyzer:
    def break_down(self, text):
        """Splits text into sentences and returns the first few key points."""
        sentences = re.split(r'(?<=[.!?])\s+', text)
        key_points = [s for s in sentences if len(s) > 20]
        return key_points[:3]
    
    def compare_sources(self, res1, res2):
        """Compares two texts and returns the longer summary if they are similar."""
        words1 = set(res1.lower().split())
        words2 = set(res2.lower().split())
        common = words1.intersection(words2)
        sim = len(common) / max(len(words1), len(words2)) if words1 and words2 else 0
        if sim > 0.6:
            return res1 if len(res1) >= len(res2) else res2
        else:
            return f"Source 1: {res1}\nSource 2: {res2}"
    
    def analyze(self, topic, bing_res, yandex_res):
        """Analyzes and merges search results, then breaks them down into key points."""
        if not bing_res and not yandex_res:
            return "No reliable information found online."
        if bing_res and yandex_res:
            merged = self.compare_sources(bing_res, yandex_res)
        elif bing_res:
            merged = bing_res
        else:
            merged = yandex_res
        breakdown = self.break_down(merged)
        summary = ". ".join(breakdown)
        return summary if summary else merged

# === CGG-EUSO Chatbot ===
class CGG_EUSO:
    def __init__(self):
        self.knowledge_file = "knowledge.json"
        self.responses_file = "responses.json"
        self.nlp_processor = NLPProcessor()
        self.thinking_chain = ThinkingChain()
        self.analyzer = Analyzer()
        self.load_data()
        # Predefined greetings that trigger casual chat
        self.greeting_phrases = {"hi", "hello", "hey", "yo", "good morning", "good afternoon", "good evening"}
        # Predefined casual responses (personality)
        self.casual_responses = [
            "I'm doing great, thanks for asking!",
            "All good here! How about you?",
            "I'm here and ready to chat!",
            "Hey there! What's up?"
        ]

    def load_data(self):
        """Loads stored knowledge and responses from files."""
        try:
            with open(self.knowledge_file, "r", encoding="utf-8") as f:
                self.knowledge = json.load(f)
        except FileNotFoundError:
            self.knowledge = {}
        try:
            with open(self.responses_file, "r", encoding="utf-8") as f:
                self.responses = json.load(f)
        except FileNotFoundError:
            self.responses = {}

    def save_data(self):
        """Saves learned knowledge and responses to files."""
        with open(self.knowledge_file, "w", encoding="utf-8") as f:
            json.dump(self.knowledge, f, ensure_ascii=False, indent=4)
        with open(self.responses_file, "w", encoding="utf-8") as f:
            json.dump(self.responses, f, ensure_ascii=False, indent=4)

    def check_online(self):
        """Checks if the computer is online by pinging Bing."""
        try:
            requests.get("http://www.bing.com", timeout=5)
            return True
        except Exception:
            return False

    # --- Basic NLP Preprocessing ---
    def nlp_preprocess(self, text):
        """Cleans text using our NLP processor."""
        text = re.sub(r"<.*?>", " ", text)  # Remove HTML tags
        text = re.sub(r"\s+", " ", text)      # Remove extra whitespace
        return self.nlp_processor.clean_text(text)

    # --- Summarize text ---
    def summarize_text(self, text, max_len=300):
        """Returns a summary of text up to max_len characters."""
        if len(text) > max_len:
            return text[:max_len] + "..."
        return text

    # --- Multi-Source Searching ---
    def search_bing(self, query):
        """Searches Bing for the query."""
        try:
            resp = requests.get(f"https://www.bing.com/search?q={query}", timeout=5)
            if resp and resp.text:
                cleaned = self.nlp_preprocess(resp.text)
                return self.summarize_text(cleaned)
        except Exception:
            pass
        return ""

    def search_yandex(self, query):
        """Searches Yandex for the query."""
        try:
            # Yandex search URL – note: Yandex does not have a free public API like DuckDuckGo.
            resp = requests.get(f"https://yandex.com/search/?text={query}", timeout=5)
            if resp and resp.text:
                cleaned = self.nlp_preprocess(resp.text)
                return self.summarize_text(cleaned)
        except Exception:
            pass
        return ""

    def fact_check(self, query):
        """
        Searches both Bing and Yandex, then uses the Analyzer and Thinking Chain
        to generate a well-reasoned, summarized answer.
        """
        bing_res = self.search_bing(query)
        yandex_res = self.search_yandex(query)
        analyzed = self.analyzer.analyze(query, bing_res, yandex_res)
        reasoning = self.thinking_chain.reason_step_by_step(query)
        if reasoning:
            return f"{reasoning}\n\n{analyzed}"
        return analyzed

    # --- Knowledge & Response Storage ---
    def learn_knowledge(self, topic, info):
        """Stores new knowledge if it's not already present or if the new info is more detailed."""
        if topic not in self.knowledge or len(info) > len(self.knowledge[topic]):
            self.knowledge[topic] = info
            self.save_data()

    def learn_response(self, question, answer):
        """Stores a direct response for a given query."""
        if question not in self.responses:
            self.responses[question] = answer
            self.save_data()

    # --- Predict & Suggest Related Questions ---
    def predict_questions(self, user_input):
        suggestions = []
        for question in self.knowledge:
            if user_input.lower() in question.lower():
                suggestions.append(question)
            else:
                sim = self.similarity(user_input, question)
                if sim > 0.3:
                    suggestions.append(question)
        return suggestions

    def similarity(self, text1, text2):
        """Computes a simple similarity score based on word overlap."""
        words1 = set(text1.lower().split())
        words2 = set(text2.lower().split())
        if not words1 or not words2:
            return 0
        common = words1.intersection(words2)
        return len(common) / max(len(words1), len(words2))

    # --- Natural Chat (Casual Conversation) ---
    def casual_chat(self, user_input):
        """
        Handles casual conversation using a set of personality-based responses.
        This function will respond naturally to greetings and opinion-based queries.
        """
        # For greetings, return a random greeting response.
        if user_input.lower() in self.greeting_phrases:
            return random.choice(self.casual_responses)
        # For other casual inputs, echo or provide a friendly reply.
        return f"That's interesting! Tell me more about that."

    # --- Main Respond Method ---
    def respond(self, user_input):
        """
        Generates a response:
          - If a direct response is stored, returns it.
          - Else if knowledge exists, returns that.
          - Else, if the input seems factual (contains keywords like what, who, when, where, why),
            and we're online, it fact-checks and uses the Thinking Chain to generate an answer.
          - Otherwise, it engages in casual chat.
          - If offline and unknown, it asks the user to teach it a response.
        """
        # If the input is a greeting or casual, handle it without searching.
        lower_input = user_input.lower()
        factual_keywords = {"what", "who", "when", "where", "why"}
        is_factual = any(word in lower_input.split() for word in factual_keywords)
        # Special case: "how are you" is casual.
        if "how are you" in lower_input:
            is_factual = False

        if lower_input in self.greeting_phrases:
            return random.choice(self.casual_responses)

        if not is_factual:
            # For non-factual inputs, use casual chat.
            return self.casual_chat(user_input)

        # If a direct response is stored, return it.
        if user_input in self.responses:
            return self.responses[user_input]

        # If stored knowledge exists, return it.
        if user_input in self.knowledge:
            return f"I found this in my knowledge: {self.knowledge[user_input]}"

        # If unknown and online, search the web.
        if self.check_online():
            answer = self.fact_check(user_input)
            if answer and "No reliable information found online." not in answer:
                self.learn_knowledge(user_input, answer)
            else:
                answer = "No reliable information found online."
        else:
            print("\n🤔 I don't know this yet. Please teach me!")
            new_response = input("Enter a response: ")
            self.learn_response(user_input, new_response)
            return "Got it! I'll remember this."

        # After answering, suggest related questions.
        suggestions = self.predict_questions(user_input)
        if suggestions:
            answer += "\n\nYou might also be interested in these related topics:\n" + "\n".join(suggestions)
        return answer

    def show_code(self):
        """Displays stored knowledge and responses for debugging."""
        print("\n📜 **CGG-EUSO Code Check Mode** 📜")
        print("\nKnowledge stored:")
        print(json.dumps(self.knowledge, ensure_ascii=False, indent=4))
        print("\nResponses stored:")
        print(json.dumps(self.responses, ensure_ascii=False, indent=4))

# === Step 3: Run the Chatbot ===
bot = CGG_EUSO()

print("\n💬 CGG-EUSO is ready! Choose a mode:")
print("1️⃣ Chat Mode")
print("2️⃣ Code Check Mode")

while True:
    mode = input("\nSelect (1/2): ").strip()
    if mode == "1":
        print("\n💬 Enter 'exit' to quit.\n")
        while True:
            user_input = input("You: ")
            if user_input.lower() == "exit":
                print("\nGoodbye! 👋")
                break
            response = bot.respond(user_input)
            print(f"CGG-EUSO: {response}")
        break
    elif mode == "2":
        bot.show_code()
        break
    else:
        print("\n❌ Invalid choice! Please enter '1' or '2'.")