from providers.alphavantage import AlphaVantageProvider
from providers.polygon import PolygonProvider
from analysis.indicators import calculate_indicators
from analysis.signals import GenerateSignal
from visualization.plot import Plotting
from config.settings import DEFAULT_DAYS, DEFAULT_LOT_SIZE
from telegram import InputFile, Update
from telegram.ext import ContextTypes
from stocks.dividends import format_dividends
from stocks.options import format_options
from stocks.earnings import format_earnings
from stocks.compare import compare_stocks
from stocks.sector import format_sector_performance
from stocks.risk import calculate_position_sizing
from stocks.portfolio import calculate_portfolio_metrics
from models.portfolio import (
		add_to_portfolio,
		get_portfolio,
		remove_from_portfolio,
)
from stocks.watchlist import calculate_watchlist_performance
from stocks.screener import filter_stocks, calculate_rsi, generate_heatmap
from stocks.education import get_educational_content
from stocks.education import get_educational_content, fetch_real_time_example, generate_indicator_chart
from models.watchlist import add_to_watchlist, remove_from_watchlist, get_watchlist

user_preferences = {}

def get_user_preference(user_id, key, default=None):
		"""Get a user's preference."""
		return user_preferences.get(user_id, {}).get(key, default)

def set_user_preference(user_id, key, value):
		"""Set a user's preference."""
		if user_id not in user_preferences:
				user_preferences[user_id] = {}
		user_preferences[user_id][key] = value

async def help(update: Update, context: ContextTypes.DEFAULT_TYPE):
		await update.message.reply_text(
				"Welcome to the Forex and Stock Analysis Bot! Here's a detailed guide on how to use the bot:\n\n"
				
				"1. **/start**\n"
				"   - Start the bot and get a welcome message.\n"
				"   - Example: `/start`\n\n"
				
				"2. **/analyze <pair> [days] [lot_size]**\n"
				"   - Analyze a forex pair or stock using technical indicators like SMA, RSI, Bollinger Bands, and MACD.\n"
				"   - The bot will generate buy/sell signals, calculate stop-loss (SL) and take-profit (TP) levels, and provide monetary risk/reward estimates.\n"
				"   - Example: `/analyze EURUSD 50 1` (Analyze EURUSD with data from the last 50 days and a lot size of 1)\n"
				"   - Notes:\n"
				"     - Default values: `days=100`, `lot_size=1`.\n"
				"     - Ensure sufficient historical data is available for analysis.\n\n"
				
				"3. **/news <symbol>**\n"
				"   - Fetch recent news articles for a stock.\n"
				"   - Example: `/news AAPL` (Fetch news for Apple Inc.)\n\n"
				
				"4. **/dividends <symbol>**\n"
				"   - Get upcoming dividend information for a stock.\n"
				"   - Example: `/dividends AAPL` (Get dividend details for Apple Inc.)\n\n"
				"5. **/options <symbol>**\n"
				"   - Fetch options data for a stock, including call and put options.\n"
				"   - Example: `/options AAPL` (Fetch options data for Apple Inc.)\n\n"
				
				"6. **/portfolio**\n"
				"   - Manage your stock portfolio.\n"
				"   - Commands:\n"
				"     - `/portfolio add <symbol> <quantity> <purchase_price>`: Add a stock to your portfolio.\n"
				"       Example: `/portfolio add AAPL 10 150` (Add 10 shares of AAPL purchased at $150 each).\n"
				"     - `/portfolio view`: View your current portfolio with metrics like current value and profit/loss.\n"
				"     - `/portfolio remove <symbol>`: Remove a stock from your portfolio.\n"
				"       Example: `/portfolio remove AAPL` (Remove AAPL from your portfolio).\n\n"
				
				"7. **/earnings <symbol>**\n"
				"   - Get upcoming earnings reports for a stock.\n"
				"   - Example: `/earnings AAPL` (Get earnings details for Apple Inc.)\n\n"
				
				"8. **/compare <symbol1> <symbol2> ...**\n"
				"   - Compare multiple stocks based on metrics like price, volume, and performance.\n"
				"   - Example: `/compare AAPL TSLA MSFT` (Compare Apple, Tesla, and Microsoft).\n\n"
				
				"9. **/sector**\n"
				"   - View sector performance for the US market.\n"
				"   - Example: `/sector` (Get performance metrics for sectors like Technology, Healthcare, etc.)\n\n"
				
				"10. **/risk <account_balance> <risk_percentage> <stop_loss_pips> <pip_value>**\n"
				"    - Calculate position sizing based on risk management principles.\n"
				"    - Example: `/risk 10000 1 50 10` (Calculate position size for a $10,000 account with 1% risk, 50-pip stop loss, and $10 pip value).\n\n"
				
				"11. **/watchlist**\n"
				"    - Manage your stock watchlist.\n"
				"    - Commands:\n"
				"      - `/watchlist add <symbol>`: Add a stock to your watchlist.\n"
				"        Example: `/watchlist add AAPL` (Add Apple Inc. to your watchlist).\n"
				"      - `/watchlist view`: View your watchlist with performance metrics.\n"
				"      - `/watchlist remove <symbol>`: Remove a stock from your watchlist.\n"
				"        Example: `/watchlist remove AAPL` (Remove Apple Inc. from your watchlist).\n\n"
				
				"12. **/screener <filters>**\n"
				"    - Screen stocks based on filters like volume, RSI, and moving averages.\n"
				"    - Example: `/screener volume>1M rsi<30` (Find stocks with volume > 1M and RSI < 30).\n\n"
				
				"13. **/learn <topic>**\n"
				"    - Learn about trading concepts like SMA, RSI, Fibonacci Retracements, Support/Resistance, and Candlestick Patterns.\n"
				"    - Commands:\n"
				"      - `/learn sma`: Learn about Simple Moving Averages (SMA).\n"
				"      - `/learn sma AAPL`: Learn about SMA with a real-time example for Apple Inc.\n"
				"      - `/learn sma AAPL chart`: Learn about SMA with a chart visualization.\n"
				"      - `/learn sma AAPL chart short_period=30 long_period=100`: Customize parameters for SMA.\n\n"
				
				"Additional Tips:\n"
				"- Combine technical analysis with fundamental analysis for better decision-making.\n"
				"- Always use stop-loss (SL) to limit downside risk.\n"
				"- Use a risk-to-reward ratio of at least 1:2 (e.g., Risk $100 to potentially gain $200).\n"
				"- For support or feedback, contact the bot developer.\n\n"
				
				"Happy trading! 🚀"
		)
		
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
		await update.message.reply_text(
				"Welcome to the Forex and Stock Analysis Bot! Here's how you can use it:\n\n"
				"/start - Show this help message.\n"
				"/analyze <pair> [days] [lot_size] [asset_type] - Analyze a forex pair or stock. "
				"Example: `/analyze EURUSD 50 1 forex` or `/analyze AAPL 50 1 stock`.\n"
				"/setprovider <provider> - Set your preferred data provider (e.g., alphavantage, polygon). "
				"Example: `/setprovider polygon`.\n"
				"/news <symbol> - Fetch recent news articles for a stock. Example: `/news AAPL`.\n"
				"/dividends <symbol> - Get upcoming dividend information for a stock. Example: `/dividends AAPL`.\n"
				"/options <symbol> - Fetch options data for a stock. Example: `/options AAPL`.\n"
				"/portfolio add <symbol> <quantity> <purchase_price> - Add a stock to your portfolio. Example: `/portfolio add AAPL 10 150`.\n"
				"/portfolio view - View your current portfolio with metrics like current value and profit/loss.\n"
				"/portfolio remove <symbol> - Remove a stock from your portfolio. Example: `/portfolio remove AAPL`.\n"
				"/earnings <symbol> - Get upcoming earnings reports for a stock. Example: `/earnings AAPL`.\n"
				"/compare <symbol1> <symbol2> ... - Compare multiple stocks. Example: `/compare AAPL TSLA MSFT`.\n"
				"/sector - View sector performance for the US market.\n"
				"/risk <account_balance> <risk_percentage> <stop_loss_pips> <pip_value> - Calculate position sizing based on risk management. "
				"Example: `/risk 10000 1 50 10`.\n\n"
				"/watchlist add <symbol> - Add a stock to your watchlist. Example: `/addwatchlist AAPL`.\n"
				"/watchlist view - View your watchlist with performance metrics.\n"
				"/watchlist remove <symbol> - Remove a stock from your watchlist. Example: `/watchlist remove AAPL`.\n"
				"/screener <filters> - Screen stocks based on filters. Example: `/screener volume>1M rsi<30`.\n"
				"/learn <topic> - Learn about trading concepts (e.g., sma, rsi). Example: `/learn sma`.\n"
				"/learn <topic> [symbol] - Learn about trading concepts with real time symbol example (e.g., sma, rsi). Example: `/learn sma APPL`.\n"
				"/learn <topic> [symbol] [chart] - Learn about trading concepts with real time symbol example and chart (e.g., sma, rsi). Example: `/learn sma APPL`.\n"
				"/learn <topic> [symbol] [chart] [...args] - Learn about trading concepts with real time symbol example chart plus filters (e.g., sma, rsi). Example: `/learn sma AAPL chart short_period=30 long_period=100`.\n"
				"Note: Replace placeholders with actual values when using the commands."
		)
		
async def setprovider(update: Update, context: ContextTypes.DEFAULT_TYPE):
		user_id = update.effective_user.id
		provider_name = context.args[0].lower() if context.args else None

		if not provider_name:
				await update.message.reply_text("Usage: /setprovider <provider>")
				await update.message.reply_text("Supported providers: alphavantage, polygon")
				return

		if provider_name in ["alphavantage", "polygon"]:
				# Save the user's preferred provider
				set_user_preference(user_id, "provider", provider_name)
				await update.message.reply_text(f"Provider set to {provider_name}.")
		else:
				await update.message.reply_text(f"Unsupported provider: {provider_name}")
				
async def analyze(update: Update, context: ContextTypes.DEFAULT_TYPE):
		user_id = update.effective_user.id
		symbol = context.args[0].upper() if context.args else None
		days = int(context.args[1]) if len(context.args) > 1 else DEFAULT_DAYS
		lot = float(context.args[2]) if len(context.args) > 2 else DEFAULT_LOT_SIZE
		asset_type = context.args[3].lower() if len(context.args) > 3 else "forex"

		if not symbol:
				await update.message.reply_text("Usage: /analyze <pair> [no_of_days] [lot_size] [asset_type] (e.g., EURUSD 50 1 forex)")
				return

		try:
				# Get the user's preferred provider
				provider_name = get_user_preference(user_id, "provider", "alphavantage")
				
				# Select the provider based on the input
				if provider_name == "alphavantage":
						provider = AlphaVantageProvider()
				elif provider_name == "polygon":
						provider = PolygonProvider()
				else:
						await update.message.reply_text(f"Unsupported provider: {provider_name}")
						return
				
				df = provider.fetch_data(symbol, asset_type=asset_type)
				df = df.resample('D').ffill().tail(days)
				
				# Validate the data
				if df.empty:
						await update.message.reply_text(f"No data available for {symbol}. Please try again later.")
						return
				if len(df) < 200:
						await update.message.reply_text(f"Not enough data for {symbol} ({len(df)} rows). Please increase the number of days.")
						return
				
				df = calculate_indicators(df)
				# signals = generate_signals(df, symbol, lot)
				signals = GenerateSignal().generate_signals(df, symbol, "alphavantage", lot)
				plot_buffer = Plotting().generate_candlesticks(df, symbol)

				message = f"Analysis for {symbol}:\n\n" + "\n".join(signals) if signals else f"No clear signals for {symbol}."
				await update.message.reply_text(message)
				await update.message.reply_photo(photo=InputFile(plot_buffer, filename=f"{symbol}_chart.png"))
		except Exception as e:
				await update.message.reply_text(f"Error analyzing {symbol}: {str(e)}")
				
async def news(update: Update, context: ContextTypes.DEFAULT_TYPE):
		symbol = context.args[0].upper() if context.args else None

		if not symbol:
				await update.message.reply_text("Usage: /news <symbol>")
				return

		try:
				provider = PolygonProvider()
				news_data = provider.fetch_news(symbol)

				if not news_data:
						await update.message.reply_text(f"No news available for {symbol}.")
						return

				message = f"Recent news for {symbol}:\n\n"
				for article in news_data[:5]:  # Show top 5 articles
						title = article.get("title", "No title")
						url = article.get("article_url", "#")
						message += f"- [{title}]({url})\n"

				await update.message.reply_text(message, parse_mode="Markdown")

		except Exception as e:
				await update.message.reply_text(f"Error fetching news for {symbol}: {str(e)}")

async def dividends(update: Update, context: ContextTypes.DEFAULT_TYPE):
		symbol = context.args[0].upper() if context.args else None

		if not symbol:
				await update.message.reply_text("Usage: /dividends <symbol>")
				return

		try:
				provider = PolygonProvider()
				dividends_data = provider.fetch_dividends(symbol)
				
				message = format_dividends(dividends_data)
				
				# Split the message into chunks of 4000 characters or less
				chunk_size = 4000
				for i in range(0, len(message), chunk_size):
						await update.message.reply_text(message[i:i + chunk_size], parse_mode="HTML")

		except Exception as e:
				await update.message.reply_text(f"Error fetching dividends for {symbol}: {str(e)}")

async def options(update: Update, context: ContextTypes.DEFAULT_TYPE):
		symbol = context.args[0].upper() if context.args else None

		if not symbol:
				await update.message.reply_text("Usage: /options <symbol>")
				return

		try:
				provider = PolygonProvider()
				options_data = provider.fetch_options(symbol)
				
				message = format_options(options_data)
				await update.message.reply_text(message)

		except Exception as e:
				await update.message.reply_text(f"Error fetching options for {symbol}: {str(e)}")

async def portfolio(update: Update, context: ContextTypes.DEFAULT_TYPE):
		user_id = update.effective_user.id
		action = context.args[0].lower() if context.args else None

		if action == "add":
				symbol = context.args[1].upper() if len(context.args) > 1 else None
				quantity = int(context.args[2]) if len(context.args) > 2 else None
				purchase_price = float(context.args[3]) if len(context.args) > 3 else None

				if not symbol or not quantity or not purchase_price:
						await update.message.reply_text("Usage: /portfolio add <symbol> <quantity> <purchase_price>")
						return

				add_to_portfolio(user_id, symbol, quantity, purchase_price)
				await update.message.reply_text(f"Added {quantity} shares of {symbol} to your portfolio.")

		elif action == "view":
				portfolio = get_portfolio(user_id)

				if not portfolio:
						await update.message.reply_text("Your portfolio is empty.")
						return

				try:
						provider = PolygonProvider()
						metrics, total_value, total_profit_loss = calculate_portfolio_metrics(portfolio, provider)

						message = "Portfolio Metrics:\n\n"
						for symbol, data in metrics.items():
								if "error" in data:
										message += f"- {symbol}: Error fetching data ({data['error']})\n"
								else:
										message += (
												f"- {symbol}: "
												f"{data['quantity']} shares @ ${data['average_purchase_price']:.2f}, "
												f"Latest Price: ${data['latest_price']:.2f}, "
												f"Current Value: ${data['current_value']:.2f}, "
												f"Profit/Loss: ${data['profit_loss']:.2f} ({data['percentage_change']:.2f}%)\n"
										)

						message += f"\nTotal Portfolio Value: ${total_value:.2f}\n"
						message += f"Total Profit/Loss: ${total_profit_loss:.2f}"

						await update.message.reply_text(message)

				except Exception as e:
						await update.message.reply_text(f"Error calculating portfolio metrics: {str(e)}")

		elif action == "remove":
				symbol = context.args[1].upper() if len(context.args) > 1 else None

				if not symbol:
						await update.message.reply_text("Usage: /portfolio remove <symbol>")
						return

				remove_from_portfolio(user_id, symbol)
				await update.message.reply_text(f"Removed {symbol} from your portfolio.")

		else:
				await update.message.reply_text(
						"Usage:\n"
						"/portfolio add <symbol> <quantity> <purchase_price>\n"
						"/portfolio view\n"
						"/portfolio remove <symbol>"
				)

async def earnings(update: Update, context: ContextTypes.DEFAULT_TYPE):
		symbol = context.args[0].upper() if context.args else None

		if not symbol:
				await update.message.reply_text("Usage: /earnings <symbol>")
				return

		try:
				provider = PolygonProvider()
				earnings_data = provider.fetch_earnings(symbol)
				
				if earnings_data.empty:
						await update.message.reply_text(f"No data available for {symbol}. Skipping...")
						return

				message = format_earnings(earnings_data)
				await update.message.reply_text(message)

		except Exception as e:
				print(e)
				await update.message.reply_text(f"Error fetching earnings for {symbol}: {str(e)}")
				
async def compare(update: Update, context: ContextTypes.DEFAULT_TYPE):
		symbols = context.args if context.args else None

		if not symbols or len(symbols) < 2:
				await update.message.reply_text("Usage: /compare <symbol1> <symbol2> ... (at least 2 symbols)")
				return

		try:
				provider = PolygonProvider()
				stocks_data_daily = {}
				stocks_data_weekly = {}
				valid_symbols = []

				for symbol in symbols:
						try:
								df = provider.fetch_stock_data(symbol)
								
								if df.empty:
										await update.message.reply_text(f"No data available for {symbol}. Skipping...")
										continue
								
								stocks_data_daily[symbol] = df.tail(1)  # Last 1 day
								stocks_data_weekly[symbol] = df.tail(5)  # Last 5 days (1 week)

								valid_symbols.append(symbol)
						except Exception as e:
								await update.message.reply_text(f"Error fetching data for {symbol}: {str(e)}")

				if len(valid_symbols) < 2:
						await update.message.reply_text("At least two valid symbols are required for comparison.")
						return

				message = compare_stocks(stocks_data_daily, stocks_data_weekly)
				await update.message.reply_text(message, parse_mode="HTML")

		except Exception as e:
				error_message = str(e)
				if "API Limit Exceeded" in error_message:
						error_message += "\nPlease wait a few minutes and try again."
				await update.message.reply_text(f"Error comparing stocks: {error_message}")

async def sector(update: Update, context: ContextTypes.DEFAULT_TYPE):
		try:
				provider = PolygonProvider()
				sector_data = provider.fetch_sector_performance()

				message = format_sector_performance(sector_data)
				await update.message.reply_text(message)

		except Exception as e:
				await update.message.reply_text(f"Error fetching sector performance: {str(e)}")

async def risk(update: Update, context: ContextTypes.DEFAULT_TYPE):
		args = context.args
		if len(args) != 4:
				await update.message.reply_text("Usage: /risk <account_balance> <risk_percentage> <stop_loss_pips> <pip_value>")
				return

		try:
				account_balance = float(args[0])
				risk_percentage = float(args[1])
				stop_loss_pips = float(args[2])
				pip_value = float(args[3])

				position_size = calculate_position_sizing(account_balance, risk_percentage, stop_loss_pips, pip_value)
				message = (
						f"Position Sizing Calculation:\n"
						f"Account Balance: ${account_balance:.2f}\n"
						f"Risk Percentage: {risk_percentage}%\n"
						f"Stop-Loss Pips: {stop_loss_pips}\n"
						f"Pip Value: ${pip_value:.2f}\n"
						f"Recommended Position Size: {position_size:.2f} units"
				)
				await update.message.reply_text(message)

		except Exception as e:
				await update.message.reply_text(f"Error calculating risk: {str(e)}")

async def watchlist(update: Update, context: ContextTypes.DEFAULT_TYPE):
		user_id = update.effective_user.id
		action = context.args[0].lower() if context.args else None
		
		try:
			if action == "add":
					symbol = context.args[1].upper() if context.args else None

					if not symbol:
							await update.message.reply_text("Usage: /watchlist add <symbol>")
							return

					add_to_watchlist(user_id, symbol)
					await update.message.reply_text(f"Added {symbol} to your watchlist.")

			elif action == "view":
					watchlist = get_watchlist(user_id)
					print(watchlist)
					if not watchlist:
							await update.message.reply_text("Your watchlist is empty.")
							return
				
					performance = calculate_watchlist_performance(watchlist)

					message = "Watchlist Performance:\n\n"
					for symbol, data in performance.items():
							if "error" in data:
									message += f"- {symbol}: Error fetching data ({data['error']})\n"
							else:
									message += (
											f"- {symbol}: "
											f"Latest Price: ${data['latest_price']:.2f}, "
											f"% Change: {data['percent_change']:.2f}%\n"
									)

					await update.message.reply_text(message)

			elif action == "remove":
					symbol = context.args[1].upper() if context.args else None

					if not symbol:
							await update.message.reply_text("Usage: /watchlist remove <symbol>")
							return

					remove_from_watchlist(user_id, symbol)
					await update.message.reply_text(f"Removed {symbol} from your watchlist.")
					
			else:
				await update.message.reply_text(
						"Usage:\n"
						"/watchlist add <symbol>\n"
						"/watchlist view\n"
						"/watchlist remove <symbol>"
				)
		except Exception as e:
				await update.message.reply_text(f"Error running watchlist: {str(e)}")

async def screener(update: Update, context: ContextTypes.DEFAULT_TYPE):
		filters = {}
		for arg in context.args:
				if ">" in arg:
						key, value = arg.split(">")
						filters[key] = f">{value}"
				elif "<" in arg:
						key, value = arg.split("<")
						filters[key] = f"<{value}"

		if not filters:
				await update.message.reply_text("Usage: /screener <filter1> <filter2> ... (e.g., volume>1M rsi<30)")
				return

		try:
				provider = PolygonProvider()
				df = provider.fetch_aggregate_data()

				# Add RSI to the DataFrame
				df = calculate_rsi(df)

				# Apply filters
				filtered_df = filter_stocks(df, filters)

				if filtered_df.empty:
						await update.message.reply_text("No stocks match the given criteria.")
						return

				# Generate heatmap
				heatmap_buffer = generate_heatmap(filtered_df)

				# Send results
				message = "Filtered Stocks:\n\n"
				for symbol in filtered_df.index.unique():
						message += f"- {symbol}\n"

				await update.message.reply_text(message)
				await update.message.reply_photo(photo=InputFile(heatmap_buffer, filename="heatmap.png"))

		except Exception as e:
				await update.message.reply_text(f"Error running screener: {str(e)}")

async def learn(update: Update, context: ContextTypes.DEFAULT_TYPE):
		args = context.args
		if not args:
				await update.message.reply_text("Usage: /learn <topic> [symbol] [chart] [parameters] (e.g., /learn sma AAPL chart period=100)")
				return

		topic = args[0].lower()
		symbol = args[1].upper() if len(args) > 1 else None
		chart_requested = "chart" in args

		# Parse custom parameters
		custom_params = {}
		for arg in args:
				if "=" in arg:
						key, value = arg.split("=")
						custom_params[key.lower()] = int(value) if value.isdigit() else value

		# Fetch static content
		content = get_educational_content(topic)
		if not content:
				await update.message.reply_text(f"No educational content available for '{topic}'.")
				return

		message = (
				f"*{content['title']}*\n\n"
				f"{content['description']}\n\n"
				f"Example: {content['example']}\n\n"
				"Resources:\n" + "\n".join(content["resources"])
		)

		# Fetch real-time example if a symbol is provided
		if symbol:
				example_message = fetch_real_time_example(symbol, topic, custom_params)
				message += f"\n\nReal-Time Example for {symbol}:\n{example_message}"

		# Generate a chart if requested
		if chart_requested and symbol:
				try:
						chart_buffer = generate_indicator_chart(symbol, topic, custom_params)
						await update.message.reply_photo(photo=InputFile(chart_buffer, filename=f"{symbol}_{topic}_chart.png"))
				except Exception as e:
						await update.message.reply_text(f"Error generating chart: {str(e)}")

		await update.message.reply_text(message, parse_mode="Markdown")