import json
import requests
import random
import string
import time
import re
import collections
from urllib.parse import parse_qs, urlencode, urlparse

# Disable SSL warnings (optional, based on your environment)
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

# Vowels list for analysis (optional, can be used for advanced strategies)
vowels = ['a', 'e', 'i', 'o', 'u']

# Load and clean the dictionary
def load_dictionary(file_path):
    try:
        with open(file_path, "r") as f:
            # Load words, convert to lowercase, and ensure they are purely alphabetic
            words = [line.strip().lower() for line in f if line.strip().isalpha()]
        return words
    except FileNotFoundError:
        print(f"Dictionary file not found at {file_path}. Please verify the path.")
        return []

class HangmanAPIError(Exception):
    """Custom exception for Hangman API errors."""
    def __init__(self, result):
        self.result = result
        self.code = None
        try:
            self.type = result["error_code"]
        except (KeyError, TypeError):
            self.type = ""
        try:
            self.message = result["error_description"]
        except (KeyError, TypeError):
            try:
                self.message = result["error"]["message"]
                self.code = result["error"].get("code")
                if not self.type:
                    self.type = result["error"].get("type", "")
            except (KeyError, TypeError):
                try:
                    self.message = result["error_msg"]
                except (KeyError, TypeError):
                    self.message = "Unknown Hangman API Error."
        Exception.__init__(self, self.message)

class HangmanAPI(object):
    def __init__(self, access_token=None, session=None, timeout=10):
        self.hangman_url = self.determine_hangman_url()
        self.access_token = access_token
        self.session = session or requests.Session()
        self.timeout = timeout
        self.guessed_letters = set()  # Use set to prevent duplicates
        
        # Update the path to your dictionary file as needed
        full_dictionary_location = "/kaggle/input/hangmen/words_250000_train.txt"  # Ensure this path is correct
        self.full_dictionary = self.build_dictionary(full_dictionary_location)
        if not self.full_dictionary:
            raise ValueError("Failed to load the dictionary. Terminating the program.")
        print(f"Loaded dictionary with {len(self.full_dictionary)} words.")
        
        # Precompute letter frequencies in the full dictionary
        self.full_dictionary_common_letter_sorted = collections.Counter("".join(self.full_dictionary)).most_common()
        
        # Initialize current dictionary
        self.current_dictionary = self.full_dictionary.copy()
        
        # Statistics
        self.total_practice_runs = 0
        self.total_recorded_runs = 0
        self.total_recorded_successes = 0
        self.total_practice_successes = 0

    @staticmethod
    def determine_hangman_url():
        """Determine the best Hangman API URL based on response times."""
        links = ['https://trexsim.com', 'https://sg.trexsim.com']
        data = {link: 0 for link in links}
        
        print("Determining the fastest Hangman API URL...")
        for link in links:
            try:
                s = time.time()
                response = requests.get(link, timeout=5)
                if response.status_code == 200:
                    elapsed = time.time() - s
                    data[link] = elapsed
                    print(f"Link {link} responded in {elapsed:.4f} seconds.")
                else:
                    data[link] = float('inf')
                    print(f"Link {link} responded with status code {response.status_code}.")
            except requests.RequestException as e:
                data[link] = float('inf')
                print(f"Link {link} failed with exception: {e}")
                
        sorted_links = sorted(data.items(), key=lambda x: x[1])
        best_link = sorted_links[0][0] if sorted_links and sorted_links[0][1] != float('inf') else 'https://trexsim.com'
        hangman_endpoint = '/trexsim/hangman'
        final_url = best_link + hangman_endpoint
        print(f"Determined Hangman URL: {final_url}")
        return final_url

    def build_dictionary(self, file_path):
        """Load the dictionary from the specified file."""
        words = load_dictionary(file_path)
        if words:
            print(f"Dictionary loaded from {file_path}.")
        return words

    def request(self, path, args=None, post_args=None, method=None):
        """Send a request to the Hangman API."""
        if args is None:
            args = {}
        if post_args is not None:
            method = "POST"

        # Add access_token to the request
        if self.access_token:
            if post_args and "access_token" not in post_args:
                post_args["access_token"] = self.access_token
            elif "access_token" not in args:
                args["access_token"] = self.access_token

        time.sleep(0.2)  # Rate limiting

        num_retry, time_sleep = 50, 2
        for attempt in range(num_retry):
            try:
                response = self.session.request(
                    method or "GET",
                    self.hangman_url + path,
                    timeout=self.timeout,
                    params=args,
                    json=post_args,
                    verify=False
                )
                response.raise_for_status()
                break
            except requests.HTTPError as e:
                try:
                    error_content = e.response.text
                    error_json = json.loads(error_content)
                    raise HangmanAPIError(error_json)
                except json.JSONDecodeError:
                    print(f"Non-JSON error response received: {e.response.text}")
                    raise HangmanAPIError({"error_msg": "HTTP Error with no content."})
            except requests.exceptions.SSLError as e:
                if attempt + 1 == num_retry:
                    raise
                print(f"SSL Error encountered. Retrying in {time_sleep} seconds...")
                time.sleep(time_sleep)
            except requests.RequestException as e:
                if attempt + 1 == num_retry:
                    raise
                print(f"Request exception encountered: {e}. Retrying in {time_sleep} seconds...")
                time.sleep(time_sleep)

        headers = response.headers
        content_type = headers.get('content-type', '')
        if 'application/json' in content_type:
            try:
                result = response.json()
            except json.JSONDecodeError:
                print(f"Failed to decode JSON response. Response Text: {response.text}")
                raise HangmanAPIError({"error_msg": "Failed to decode JSON response."})
        else:
            # Handle non-json responses gracefully
            try:
                result = json.loads(response.text)
            except json.JSONDecodeError:
                print(f"Non-JSON response received: {response.text}")
                raise HangmanAPIError({"error_msg": "Response content type not supported."})
        
        if isinstance(result, dict) and result.get("error"):
            print(f"API Error: {result.get('error')}")
            raise HangmanAPIError(result)
        return result

    def build_state(self, word, guessed_letters, tries_left):
        """Create a hashable state from the current word, guessed letters, and tries left."""
        clean_word = word.replace(" ", "").lower()  # Remove spaces and lowercase
        state = (clean_word, "".join(sorted(guessed_letters)), tries_left)
        return state

    def start_game(self, practice=True, verbose=True):
        """Play a full game of hangman, implementing the frequency-based guessing."""
        # Reset guessed letters and dictionary
        self.guessed_letters = set()
        self.current_dictionary = self.full_dictionary.copy()
        
        # Initialize status
        status = "failed"  # Default to failed unless proven otherwise
        
        # Start new game with GET method and 'practice' parameter
        try:
            response = self.request("/new_game", args={"practice": int(practice)}, post_args=None, method="GET")
        except HangmanAPIError as e:
            if verbose:
                print(f"HangmanAPIError during new_game: {e.message}")
            return False
        except Exception as e:
            if verbose:
                print(f"Unexpected error during new_game: {e}")
            return False

        if response.get('status') == "approved":
            game_id = response.get('game_id')
            word = response.get('word')  # Example: "_ p p _ e "
            tries_remains = response.get('tries_remains')
            
            if word is None:
                if verbose:
                    print("Warning: 'word' not found in the response. Proceeding without initial word state.")
                word = ""
            
            if verbose:
                print(f"New game started. Game ID: {game_id}. Tries left: {tries_remains}. Word: {word}")
            
            # Update statistics
            if practice:
                self.total_practice_runs += 1
            else:
                self.total_recorded_runs += 1
            
            state = self.build_state(word, self.guessed_letters, tries_remains)
            
            while tries_remains > 0:
                # Guess based on frequency-based algorithm
                guess_letter = self.guess(word)
                if not guess_letter:
                    if verbose:
                        print("No available letters to guess.")
                    break
                self.guessed_letters.add(guess_letter)
                
                if verbose:
                    print(f"Guessing letter: '{guess_letter}'")
                
                # Make the guess and get response using GET method
                try:
                    res = self.request(
                        "/guess_letter",
                        args={
                            "request": "guess_letter",
                            "game_id": game_id,
                            "letter": guess_letter
                        },
                        post_args=None,      # Ensure no POST args are sent
                        method="GET"         # Use GET instead of POST
                    )
                except HangmanAPIError as e:
                    if verbose:
                        print(f"HangmanAPIError during guess_letter: {e.message}")
                    break
                except Exception as e:
                    if verbose:
                        print(f"Unexpected error during guess_letter: {e}")
                    break
                
                if verbose:
                    print(f"Server response: {res}")
                
                status = res.get('status', 'failed')  # Default to 'failed' if not provided
                tries_remains = res.get('tries_remains', tries_remains)
                correct_guess = res.get('correct_guess', False)
                new_word = res.get('word', word)
                
                if new_word:
                    new_word = new_word.lower()
                else:
                    new_word = word.lower()
                
                # Update state
                next_state = self.build_state(new_word, self.guessed_letters, tries_remains)
                
                # Update state
                state = next_state
                
                if status == "success":
                    if verbose:
                        print("Game won!")
                    if practice:
                        self.total_practice_successes += 1
                    else:
                        self.total_recorded_successes += 1
                    break
                elif status == "failed":
                    if verbose:
                        print("Game lost.")
                    break
                elif status == "ongoing":
                    word = new_word
        else:
            if verbose:
                print("Failed to start a new game.")
                print(f"Response: {response}")
            return False
        return status == "success"

    def guess(self, word):
        """
        Enhanced guess function based on letter frequency in possible words with vowel prioritization.
        """
        # Clean the word by removing spaces and converting to lowercase
        clean_word = word.replace(" ", "").lower()
        
        # If this is the first guess, initialize possible dictionary based on word length
        if not self.guessed_letters:
            self.current_dictionary = [w for w in self.full_dictionary if len(w) == len(clean_word)]
        else:
            # Build regex pattern from the current mask
            pattern = ''.join([c if c != '_' else '.' for c in clean_word])
            regex = re.compile(f"^{pattern}$")
            
            # Filter current dictionary based on the pattern and guessed letters
            self.current_dictionary = [
                w for w in self.current_dictionary
                if regex.match(w) and all(letter in w for letter in self.guessed_letters if letter != '_')
            ]
        
        # Count letter frequencies in the remaining possible words
        letter_counts = collections.Counter()
        for word_candidate in self.current_dictionary:
            unique_letters = set(word_candidate) - self.guessed_letters  # Exclude already guessed letters
            letter_counts.update(unique_letters)
        
        if not letter_counts:
            # Fallback to overall frequency without already guessed letters
            letter_counts = collections.Counter()
            for letter, count in self.full_dictionary_common_letter_sorted:
                if letter not in self.guessed_letters:
                    letter_counts[letter] += count
            if not letter_counts:
                # As a last resort, choose a random unguessed letter
                available_letters = set(string.ascii_lowercase) - self.guessed_letters
                return random.choice(list(available_letters)) if available_letters else None
        
        # **Prioritize vowels**
        vowels_unopened = [letter for letter in vowels if letter not in self.guessed_letters]
        if vowels_unopened:
            # Select the most frequent unguessed vowel
            vowel_counts = {letter: letter_counts[letter] for letter in vowels_unopened if letter in letter_counts}
            if vowel_counts:
                # Choose the vowel with the highest frequency
                guess_letter = max(vowel_counts, key=vowel_counts.get)
                return guess_letter
        
        # If no vowels left, select the most frequent unguessed consonant
        guess_letter, _ = letter_counts.most_common(1)[0]
        
        return guess_letter

    def my_status(self):
        """Get game statistics."""
        try:
            response = self.request("/my_status", args=None, post_args=None, method="GET")
            if isinstance(response, list) and len(response) == 4:
                return response
            else:
                raise HangmanAPIError({"error_msg": "Unexpected response format for my_status."})
        except HangmanAPIError as e:
            print(f"HangmanAPIError: {e.message}")
            return [self.total_practice_runs, self.total_recorded_runs, self.total_recorded_successes, self.total_practice_successes]
        except Exception as e:
            print(f"Unexpected error while fetching status: {e}")
            return [self.total_practice_runs, self.total_recorded_runs, self.total_recorded_successes, self.total_practice_successes]

if __name__ == "__main__":
    # Initialize HangmanAPI with your access token
    api = HangmanAPI(access_token="775976eaf5c7653d7f9cc3f826ad3e", timeout=10)
    
    # Start a single practice game with verbose logging to monitor progress
    print("Starting a single practice game with verbose output for debugging...")
    success = api.start_game(practice=True, verbose=True)
    
    # Retrieve and display status after the single game
    status = api.my_status()
    if isinstance(status, list) and len(status) == 4:
        total_practice_runs, total_recorded_runs, total_recorded_successes, total_practice_successes = status
    else:
        # Fallback to internal counters if the response is not as expected
        total_practice_runs = api.total_practice_runs
        total_recorded_runs = api.total_recorded_runs
        total_recorded_successes = api.total_recorded_successes
        total_practice_successes = api.total_practice_successes
    
    # Calculate success rates safely
    if total_recorded_runs > 0:
        recorded_success_rate = total_recorded_successes / total_recorded_runs
    else:
        recorded_success_rate = 0.0
    
    if total_practice_runs > 0:
        practice_success_rate = total_practice_successes / total_practice_runs
    else:
        practice_success_rate = 0.0
    
    print(f'Run {total_practice_runs} practice games out of an allotted 100,000.')
    print(f'Practice success rate: {practice_success_rate:.2%}')
    print(f'Overall success rate = {recorded_success_rate:.3f}')
    
    # Play multiple practice games
    for i in range(1, 1001):
        print(f'Playing {i}th practice game')
        api.start_game(practice=True, verbose=False)
        time.sleep(3)  # Adjust sleep time according to rate limits
    
    # **Retrieve and display final status after all games**
    status = api.my_status()
    if isinstance(status, list) and len(status) == 4:
        total_practice_runs, total_recorded_runs, total_recorded_successes, total_practice_successes = status
    else:
        # Fallback to internal counters if the response is not as expected
        total_practice_runs = api.total_practice_runs
        total_recorded_runs = api.total_recorded_runs
        total_recorded_successes = api.total_recorded_successes
        total_practice_successes = api.total_practice_successes
    
    # Calculate final success rates safely
    if total_recorded_runs > 0:
        recorded_success_rate = total_recorded_successes / total_recorded_runs
    else:
        recorded_success_rate = 0.0
    
    if total_practice_runs > 0:
        practice_success_rate = total_practice_successes / total_practice_runs
    else:
        practice_success_rate = 0.0
    
    print(f'After {total_practice_runs} practice games:')
    print(f'Practice success rate: {practice_success_rate:.2%}')
    print(f'Overall success rate = {recorded_success_rate:.3f}')
    
    # To start recorded games (once finalized), uncomment and use cautiously
    """
    for i in range(1, 1001):
        print(f'Playing {i}th recorded game')
        api.start_game(practice=False, verbose=False)
        time.sleep(3)  # Adjust sleep time according to rate limits
     """
in this structure put deep heavy lstm and refinforcement learning, give me complete code after putting all these stems give me one code all compete to run in kaggle