import os
import time
import pandas as pd
import pandas_ta as ta
import logging
from datetime import datetime, timedelta
import pytz
from thefirstock import thefirstock
from typing import List
import numpy as np
import requests
import sys


class StockDataFetcher:
    def __init__(self, client_details: List[str]):
        self.client_details = client_details
        self.user_id = client_details[0]
        self.ist = pytz.timezone('Asia/Kolkata')
        self.logger = self.setup_logger()
        print("\n\n\n\n")
        self.logger.info("**************************")

    def setup_logger(self) -> logging.Logger:
        logger = logging.getLogger(__name__)
        logger.setLevel(logging.INFO)
        if not logger.handlers:
            formatter = self.ISTFormatter('%(asctime)s - %(levelname)s - %(message)s')
            console_handler = logging.StreamHandler()
            console_handler.setFormatter(formatter)
            logger.addHandler(console_handler)
        logging.getLogger().handlers.clear()
        return logger

    class ISTFormatter(logging.Formatter):
        def formatTime(self, record, datefmt=None):
            ist = pytz.timezone('Asia/Kolkata')
            record_time = datetime.fromtimestamp(record.created, tz=ist)
            return record_time.strftime(datefmt or '%Y-%m-%d %H:%M:%S')

    def login(self):
        try:
            response = thefirstock.firstock_login(*self.client_details)
            if response.get("status") == "success":
                self.logger.info("Login successful")
            else:
                self.logger.error(f"Login failed: {response}")
        except Exception as e:
            self.logger.error(f"Login error: {e}")

    def fetch_time_price_series(
        self, exchange: str, trading_symbol: str, start_time: str, end_time: str, interval: str
    ) -> pd.DataFrame:
        
        print('\n')
        
        self.logger.info(f"----------------------------------")
        self.logger.info(f"Fetching data for {trading_symbol}")
        self.logger.info(f"----------------------------------")
        try:
            response = thefirstock.firstock_TimePriceSeries(
                userId=self.user_id,
                exchange=exchange,
                tradingSymbol=trading_symbol,
                startTime=start_time,
                endTime=end_time,
                interval=interval,
            )
            if response.get("status") == "success":
                return pd.DataFrame(response.get("data", []))
            else:
                self.logger.error(f"Fetch failed: {response}")
                return pd.DataFrame()
        except Exception as e:
            self.logger.error(f"Error fetching series: {e}")
            return pd.DataFrame()
        
    def getStepValueAndShortSymbol(self, symbol):
        tmp_symbol=symbol
        if symbol == 'NSE:Nifty 50':
            step_value = 50
            tmp_symbol='NIFTY'
        elif symbol == 'NSE:Nifty Bank':
            step_value = 100
            tmp_symbol='BANKNIFTY'
        elif symbol == 'NSE:NIFTY MID SELECT':
            step_value = 25
            tmp_symbol='MIDCPNIFTY'
        elif symbol == 'NSE:Nifty Fin Service':
            step_value = 50
            tmp_symbol='FINNIFTY'
        elif symbol == 'BSE:SENSEX':
            step_value = 100
            tmp_symbol='SENSEX'
        else:
            step_value = 50

        return step_value, tmp_symbol
    
    def calculate_supertrend(self, df: pd.DataFrame, atr_period: int, multiplier: float) -> pd.DataFrame:
        df[['high', 'low', 'close']] = df[['inth', 'intl', 'intc']].apply(pd.to_numeric, errors='coerce')
        supertrend = df.ta.supertrend(high='high', low='low', close='close', length=atr_period, multiplier=multiplier)
        supertrend['ST_Signal'] = supertrend['SUPERTd_21_6.3'].apply(lambda x: 'Buy' if x == 1 else 'Sell')
        #supertrend['ST_Signal'] = supertrend['SUPERTd_6_13.0'].apply(lambda x: 'Buy' if x == 1 else 'Sell')
        return pd.concat([df, supertrend], axis=1)

    def process_symbol_data(self, symbol: str, interval: int, start_time: datetime, end_time: datetime):
        step_value, tmp_symbol = self.getStepValueAndShortSymbol(symbol)
        exchange, trading_symbol = symbol.split(":")

        # Fetch Data
        if exchange == 'NSE' and trading_symbol == 'Nifty 50':

            exchange = 'NFO'
            trading_symbol = 'NIFTY27FEB25F'
            
            df = self.fetch_time_price_series(
                exchange, trading_symbol,
                start_time.strftime("%d/%m/%Y %H:%M:%S"),
                end_time.strftime("%d/%m/%Y %H:%M:%S"),
                str(interval),
            )
        
        else:
            df = self.fetch_time_price_series(
                exchange, trading_symbol,
                start_time.strftime("%d/%m/%Y %H:%M:%S"),
                end_time.strftime("%d/%m/%Y %H:%M:%S"),
                str(interval),
            )

        
        if df.empty:
            self.logger.warning(f"No data for {trading_symbol}")
            return

        # Preprocess timestamps and ensure data integrity
        #df['time'] = pd.to_datetime(df['time']).dt.tz_localize(self.ist)
        df['time'] = pd.to_datetime(df['time'], errors='coerce', dayfirst=True)
        if not df['time'].dt.tz:
            df['time'] = df['time'].dt.tz_localize(self.ist)

        df = df[~((df['time'].dt.hour == 9) & (df['time'].dt.minute < 15))]
        df = df[~(((df['time'].dt.hour == 15) & (df['time'].dt.minute >= 31)) | ((df['time'].dt.hour > 15) & (df['time'].dt.hour < 23)))]
        current_time = datetime.now(self.ist)
        df = df.sort_values(by='time', ascending=True)
        
        df['intc'] = pd.to_numeric(df['intc'], errors='coerce')

        df = self.calculate_supertrend(df, 21, 6.3)
        #df = self.calculate_supertrend(df, 6, 13)
        df['SUPERTd_21_6.3'] = pd.to_numeric(df['SUPERTd_21_6.3'], errors='coerce')
        #df['SUPERTd_6_13.0'] = pd.to_numeric(df['SUPERTd_6_13.0'], errors='coerce')

        latest_date = df['time'].dt.date.max()
        df = df[df['time'].dt.date == latest_date]

        ft_high = df.iloc[0]['inth']
        ft_low = df.iloc[0]['intl']
        df['FT_High'] = ft_high
        df['FT_Low'] = ft_low

        df['FT_High'] = pd.to_numeric(df['FT_High'], errors='coerce')
        df['FT_Low'] = pd.to_numeric(df['FT_Low'], errors='coerce')

        df = df.loc[:, ['time', 'intc', 'ST_Signal', 'FT_High', 'FT_Low']]

        current_time_ist = datetime.now(self.ist).strftime('%H:%M')
        cutoff_time = "09:16"
        if current_time_ist <= cutoff_time:
            print(f"Current Time {current_time_ist} is less than or equal to {cutoff_time}. Do Nothing.")
            return
        
        current_time = datetime.now(self.ist)
        df['elapsedTime'] = (current_time - df['time']).dt.total_seconds() // 60
        df = df[df['elapsedTime'] >= interval]

        if symbol == 'NSE:Nifty 50':
            step_value = 50
            margin = 20
            tmp_symbol='NIFTY'
        elif symbol == 'NSE:Nifty Bank':
            step_value = 100
            margin = 50
            tmp_symbol='BANKNIFTY'
        elif symbol == 'NSE:NIFTY MID SELECT':
            step_value = 25
            margin = 10
            tmp_symbol='MIDCPNIFTY'
        elif symbol == 'NSE:Nifty Fin Service':
            step_value = 50
            margin = 20
            tmp_symbol='FINNIFTY'
        elif symbol == 'BSE:SENSEX':
            step_value = 100
            margin = 50
            tmp_symbol='SENSEX'
        else:
            step_value = 50
            margin = 10
            tmp_symbol='XXX'


        df['instrument'] = df['intc'].apply(lambda x: int((int(x / step_value) * step_value)))

        #print(df)

        # Initialize flags and variables
        st_signal = df.iloc[1]['ST_Signal']
        st_instrument = df.iloc[1]['instrument']
        st_time = df.iloc[1]['time']
        tickTime_str = st_time.strftime("%Y-%m-%d_%H:%M:%S")

        first_buy_order_placed = False
        first_sell_order_placed = False

        if st_signal == 'Buy':
            print(f"Place first Buy order. Instrument {st_instrument}. Time {st_time}. Close Price {df.iloc[1]['intc']}")
            first_buy_order_placed = True
            buy_order_time = st_time
            instrument = str(tmp_symbol)+str(st_instrument) + 'CE'
            closePrice = str(df.iloc[1]['intc'])
            signal = st_signal.upper()
            orderType = 'first'
            #self.createEntry(tickTime_str, instrument, closePrice, signal, orderType)
        elif st_signal == 'Sell':
            st_instrument = step_value + st_instrument
            print(f"Place first Sell order. Instrument {st_instrument}. Time {st_time}. Close Price {df.iloc[1]['intc']}")
            first_sell_order_placed = True
            sell_order_time = st_time
            instrument = str(tmp_symbol)+str(st_instrument) + 'PE'
            closePrice = str(df.iloc[1]['intc'])
            signal = st_signal.upper()
            orderType = 'first'
            #self.createEntry(tickTime_str, instrument, closePrice, signal, orderType)

        # Initialize flags for top-up orders
        topup_buy_order_placed = False
        topup_sell_order_placed = False

        topup_buy_order_price = 0.0
        topup_sell_order_price = 0.0

        new_buy_topup_allowed = True
        new_sell_topup_allowed = True

        # Iterate over the DataFrame starting from the 3rd row (index 2)
        for i in range(2, len(df)):
            row = df.iloc[i]
            current_signal = row['ST_Signal']
            current_instrument = row['instrument']
            current_time = row['time']
            current_intc = row['intc']
            current_FT_Low = row['FT_Low']
            current_FT_High = row['FT_High']
            tickTime_str = current_time.strftime("%Y-%m-%d_%H:%M:%S")

            #print(f"Tick Time: {tickTime_str}, Signal: {current_signal}, Instrument: {current_instrument}, Time: {current_time}, INTC: {current_intc}, FT Low: {current_FT_Low}, FT High: {current_FT_High}")

            # Detect change in ST_Signal and place first order accordingly
            if current_signal != st_signal:  # Check if signal has changed
                if current_signal == 'Buy' and not first_buy_order_placed:
                    #print(f"Place first Buy order. Instrument {current_instrument}. Time {current_time}. Close Price {current_intc}")
                    first_buy_order_placed = True
                    first_sell_order_placed = False
                    topup_buy_order_placed = False
                    topup_sell_order_placed = False
                    buy_order_time = current_time
                    instrument = str(tmp_symbol)+str(current_instrument) + 'CE'
                    closePrice = str(current_intc)
                    signal = current_signal.upper()
                    orderType = 'first'
                    self.createEntry(tickTime_str, instrument, closePrice, signal, orderType)
                elif current_signal == 'Sell' and not first_sell_order_placed:
                    current_instrument = step_value + current_instrument
                    #print(f"Place first Sell order. Instrument {current_instrument}. Time {current_time}. Close Price {current_intc}")
                    first_sell_order_placed = True
                    first_buy_order_placed = False
                    topup_buy_order_placed = False
                    topup_sell_order_placed = False
                    sell_order_time = current_time
                    instrument = str(tmp_symbol)+str(current_instrument) + 'PE'
                    closePrice = str(current_intc)
                    signal = current_signal.upper()
                    orderType = 'first'
                    self.createEntry(tickTime_str, instrument, closePrice, signal, orderType)

            # Update the signal for the next iteration
            st_signal = current_signal


            
            if topup_buy_order_price != 0.0 and current_intc > (topup_buy_order_price + margin) and not new_buy_topup_allowed:
                new_buy_topup_allowed = True
                #print(i, current_time, current_intc)
                continue

            if topup_sell_order_price != 0.0 and current_intc < (topup_sell_order_price - margin) and not new_sell_topup_allowed:
                new_sell_topup_allowed = True
                #print(i, current_time, current_intc)
                continue

            # Place top-up Buy or Sell orders based on the conditions
            if topup_buy_order_placed or topup_sell_order_placed:
                # If intc is within FT_Low and FT_High, reset the flag to allow new orders
                if current_intc >= current_FT_Low and current_intc <= current_FT_High:
                    if new_buy_topup_allowed:
                        topup_buy_order_placed = False
                    if new_sell_topup_allowed:
                        topup_sell_order_placed = False
                else:
                    continue  # Skip this row if a top-up order has already been placed



            # Check if the current row's ST_Signal is 'Buy' and place a top-up Buy order
            if current_signal == 'Buy' and first_buy_order_placed and buy_order_time != current_time:
                if current_intc < current_FT_Low or current_intc > current_FT_High:
                    if not topup_buy_order_placed and new_buy_topup_allowed:
                        #print(f"Top-up Buy order placed for row {i} at time {current_time}. Instrument {current_instrument}. Close Price {current_intc}")
                        topup_buy_order_placed = True
                        topup_buy_order_price = current_intc
                        new_buy_topup_allowed = False
                        instrument = str(tmp_symbol)+str(current_instrument) + 'CE'
                        closePrice = str(current_intc)
                        signal = current_signal.upper()
                        orderType = 'topUp'
                        self.createEntry(tickTime_str, instrument, closePrice, signal, orderType)

            # Check if the current row's ST_Signal is 'Sell' and place a top-up Sell order
            elif current_signal == 'Sell' and first_sell_order_placed and sell_order_time != current_time:
                if current_intc < current_FT_Low or current_intc > current_FT_High:
                    if not topup_sell_order_placed and new_sell_topup_allowed:
                        current_instrument = step_value + current_instrument
                        #print(f"Top-up Sell order placed for row {i} at time {current_time}. Instrument {current_instrument}. Close Price {current_intc}")
                        topup_sell_order_placed = True
                        topup_sell_order_price = current_intc
                        new_sell_topup_allowed = False
                        instrument = str(tmp_symbol)+str(current_instrument) + 'PE'
                        closePrice = str(current_intc)
                        signal = current_signal.upper()
                        orderType = 'topUp'
                        self.createEntry(tickTime_str, instrument, closePrice, signal, orderType)

            

    def createEntry(self, tickTime_str, instrument, closePrice, signal, orderType):

        url = "http://143.244.141.41/php/createEntry.php"  # Replace with your actual URL


        # Define the parameters
        params = {
            'tickTime': str(tickTime_str),
            'instrument': str(instrument),
            'closePrice': str(closePrice),
            'signal': str(signal),
            'orderType': str(orderType)
        }

        # Perform the GET request
        self.logger.info(f"Get Request with params: {params}")
        response = requests.get(url, params=params)
        if response.status_code != 200:
            self.logger.error(f"Request failed with status code: {response.status_code}")
            if response.text != '':
                self.logger.error(response.text + "\n")  # Print error message if available
        else:
            self.logger.info(f"Request success: {response.text}\n")


    def fetch_all_data(self):
        
        
        symbols = {
            "NSE:Nifty 50": 7,
            "NSE:Nifty Bank": 7,
            "NSE:NIFTY MID SELECT": 7,
            "NSE:Nifty Fin Service": 7,
            "BSE:SENSEX": 7
        }

        for interval in [1]:
            for symbol, days in symbols.items():
                time.sleep(1)
                start_time = datetime.now(self.ist) - timedelta(days=days)
                end_time = datetime.now(self.ist).replace(hour=15, minute=30)
                self.process_symbol_data(symbol, interval, start_time, end_time)


if __name__ == "__main__":
    client_details = ['GA0810', 'O9i8u7y6$$', '08101994', 'GA0810_API', 'ada14c5f73f182ca724b90c5dde1e72d']
    stock_fetcher = StockDataFetcher(client_details)
    stock_fetcher.login()
    stock_fetcher.fetch_all_data()
