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
import glob


class StockDataFetcher:
    def __init__(self, client_details: List[str]):
        self.client_details = client_details
        self.user_id = client_details[0]
        self.order_cutoff_time = "14:29"           # No new orders after this time
        self.forced_sell_time = "14:52"            # All outsanding orders will be closed after this
        self.forced_sell_enabled = True           # True - force close enabled; False - force close disabled
        self.stop_loss_points = 74
        self.number_of_sl =0
        self.number_of_days_with_no_data =0
        self.number_of_days_played =0
        self.number_of_positive_days =0
        self.total = 0
        self.historyTotal = 0

        self.nifty_step_value = 50
        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:
        
       
        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 process_symbol_data(self, symbol: str, interval: int, start_time: datetime, end_time: datetime):

        print(f'\n\nProcessing for day : {start_time.strftime("%d/%m/%Y")}')
        print('===============')
        self.total = 0

        exchange, trading_symbol = symbol.split(":")
        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 len(df) < 15:
            print('Not enough data to process.')
            self.number_of_days_with_no_data = self.number_of_days_with_no_data + 1
            print (f"Overall Total : {self.historyTotal} (played: {self.number_of_days_played} +ve: {self.number_of_positive_days} skip:{self.number_of_days_with_no_data} SL-C : {self.number_of_sl})\n\n")
            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 < 14))]
        #df = df[~(((df['time'].dt.hour == 15) & (df['time'].dt.minute >= 31)) | ((df['time'].dt.hour > 15) & (df['time'].dt.hour < 23)))]
        df = df[~(((df['time'].dt.hour == 14) & (df['time'].dt.minute >= 53)) | ((df['time'].dt.hour >= 15) ))]

        #print(df)

        df = df.sort_values(by='time', ascending=True)
        
        df['intc'] = pd.to_numeric(df['intc'], errors='coerce')
        df['intv'] = pd.to_numeric(df['intv'], errors='coerce')
        df['into'] = pd.to_numeric(df['into'], errors='coerce')
        df['inth'] = pd.to_numeric(df['inth'], errors='coerce')
        df['intl'] = pd.to_numeric(df['intl'], errors='coerce')

        df['date'] = df['time'].dt.tz_convert(self.ist).dt.date

        ohlc = {
            'open': df.iloc[0]['into'],  # Open from the first entry
            'close': df.iloc[4]['intc'],  # Close from the 15th entry
            'high': df.iloc[:5]['inth'].max(),  # Highest value from the first 15 entries
            'low': df.iloc[:5]['intl'].min()  # Lowest value from the first 15 entries
        }


        ppDict = self.calculate_pivot_levels(ohlc)
        print(f"Pivot Levels : {ppDict}")
        #ppDict = {'PP': df.iloc[:15]['inth'].min(), 'R1': df.iloc[:15]['intl'].max(), 'R2': 25000, 'R3': 30000, 'R3_SL': 30100, 'S1': 20000, 'S2': 15000, 'S3': 10000, 'S3_SL': 10900}

        if abs(ppDict['PP'] - ppDict['R3']) < 100 or abs(ppDict['PP'] - ppDict['S3']) < 100:
            print('Not enough movement ... Skip')
            #return

        df = df.iloc[15:].reset_index(drop=True)

        # Flags to track different buy/sell conditions separately along with buy price
        position_flags = {
            'ce_r1_buy': None,  # CE Buy at R1
            'ce_pp_buy': None,  # CE Buy at Pivot + abs_allowed_diff

            'pe_s1_buy': None,  # PE Buy at S1
            'pe_pp_buy': None,  # PE Buy at Pivot - abs_allowed_diff

        }

        abs_allowed_diff = 5  # Allowed absolute difference

        # Define buy, sell, and stop-loss levels
        CEbuyLevel = ['S3', 'S3', 'S3', 'S3', 'S2', 'S2']
        CEsellLevel = ['S2', 'S1', 'PP', 'R1', 'S1', 'PP']
        
        PEbuyLevel = ['R3', 'R3', 'R3', 'R3', 'R2', 'R2']
        PEsellLevel = ['R2', 'R1', 'PP', 'S1', 'R1', 'PP']

        
        CEbuyLevel = ['S3', 'S3', 'S3', 'S3', 'S3', 'S3']
        CEsellLevel = ['S2', 'S1', 'PP', 'R1', 'R2', 'R3']
        
        PEbuyLevel = ['R3', 'R3', 'R3', 'R3', 'R3', 'R3']
        PEsellLevel = ['R2', 'R1', 'PP', 'S1', 'S2', 'S3']
        
        CEbuyLevel = ['S3', 'S3', 'S3', 'S3', 'S3', 'S3', 'S1', 'S1', 'R1', 'R1']
        CEsellLevel = ['S2', 'S1', 'PP', 'R1', 'R2', 'R3', 'PP', 'R1', 'R2', 'R3']
        
        PEbuyLevel = ['R3', 'R3', 'R3', 'R3', 'R3', 'R3', 'R1', 'R1', 'S1', 'S1']
        PEsellLevel = ['R2', 'R1', 'PP', 'S1', 'S2', 'S3', 'PP', 'S1', 'S2', 'S3']
        
        CEbuyLevel = ['S3', 'S3', 'S3', 'S3', 'S3', 'S3', 'S1', 'S1', 'S1', 'S1', 'R1', 'R1']
        CEsellLevel = ['S2', 'S1', 'PP', 'R1', 'R2', 'R3', 'PP', 'R1', 'R2', 'R3', 'R2', 'R3']
        
        PEbuyLevel = ['R3', 'R3', 'R3', 'R3', 'R3', 'R3', 'R1', 'R1', 'R1', 'R1', 'S1', 'S1']
        PEsellLevel = ['R2', 'R1', 'PP', 'S1', 'S2', 'S3', 'PP', 'S1', 'S2', 'S3', 'S2', 'S3']

        
        CEbuyLevel = ['S3', 'S3', 'S3', 'S3', 'S3', 'S3',  'S2','S2','S2','S2','S2', 'S1','S1','S1','S1', 'PP', 'PP', 'PP', 'R1', 'R1', 'R2']
        CEsellLevel = ['S2', 'S1', 'PP', 'R1', 'R2', 'R3', 'S1','PP','R1','R2','R3', 'PP','R1','R2','R3', 'R1', 'R2', 'R3', 'R2', 'R3', 'R3']
        
        PEbuyLevel = ['R3', 'R3', 'R3', 'R3', 'R3', 'R3', 'R2', 'R2', 'R2', 'R2', 'R2', 'R1', 'R1', 'R1', 'PP', 'PP', 'PP', 'S1', 'S1', 'S2']
        PEsellLevel = ['R2', 'R1', 'PP', 'S1', 'S2', 'S3', 'R1', 'PP', 'S1', 'S2', 'S3', 'S1', 'S2', 'S3','S1', 'S2', 'S3', 'S2', 'S3', 'S3']



        CEbuyLevel = ['S3', 'S3', 'S3', 'S3', 'S3', 'S3', 'S1', 'S1', 'S1', 'S1', 'R1', 'R1']
        CEsellLevel = ['S2', 'S1', 'PP', 'R1', 'R2', 'R3', 'PP', 'R1', 'R2', 'R3', 'R2', 'R3']
        
        PEbuyLevel = ['R3', 'R3', 'R3', 'R3', 'R3', 'R3', 'R1', 'R1', 'R1', 'R1', 'S1', 'S1']
        PEsellLevel = ['R2', 'R1', 'PP', 'S1', 'S2', 'S3', 'PP', 'S1', 'S2', 'S3', 'S2', 'S3']

        
        PEbuyLevel = ['R3', 'R3', 'R3']
        PEsellLevel = ['S1', 'S2', 'S3']

        CEbuyLevel = ['S3', 'S3', 'S3']
        CEsellLevel = ['R1', 'R2', 'R3']

     
        PEbuyLevel = ['R1', 'R1']
        PEsellLevel = ['S1', 'S2' ]

        CEbuyLevel = ['S1', 'S1']
        CEsellLevel = ['R1', 'R2']
 


        # Track positions
        position_flags = {i: None for i in range(len(CEbuyLevel) + len(PEbuyLevel))}
        
        abs_allowed_diff = 5

        #print(df)

        do_not_transact_anymore = False
        
        for index, row in df.iterrows():
            intc = row['intc']
            inth = row['inth']
            intl = row['intl']
            time = row['time']
            new_order_time = time.strftime("%H:%M")
            strikePrice = round(intc / self.nifty_step_value) * self.nifty_step_value
            tmp_symbol='NIFTY'

            if do_not_transact_anymore :
                continue
            
            # CE Buy Conditions
            for i, level in enumerate(CEbuyLevel):
                level_value = ppDict[level]
                if level_value <= intl <= level_value + abs_allowed_diff and position_flags[i] is None:
                    print(f"Row {index}: CE Buy ({level}) - time: {time}, intc: {intc}")
                    instrument = tmp_symbol + str(strikePrice) + 'CE'
                    position_flags[i] = {'intc':intc, 'instrument':instrument , 'index':i}
                    
                    closePrice = inth
                    signal = 'BUY'
                    orderType = i
                    self.createEntry(time, instrument, closePrice, signal, orderType)
                    
            
            # PE Buy Conditions
            for i, level in enumerate(PEbuyLevel, start=len(CEbuyLevel)):
                level_value = ppDict[level]
                if level_value - abs_allowed_diff <= inth <= level_value and position_flags[i] is None:
                    print(f"Row {index}: PE Buy ({level}) - time: {time}, intc: {intc}")
                    instrument = tmp_symbol + str(strikePrice) + 'PE'
                    position_flags[i] = {'intc':intc, 'instrument':instrument, 'index':i}
                    closePrice = inth
                    signal = 'BUY'
                    orderType = i
                    self.createEntry(time, instrument, closePrice, signal, orderType)
            
            # CE Sell Conditions
            for i, buy_level in enumerate(CEbuyLevel):
                sell_level = CEsellLevel[i]
                level_value = ppDict[sell_level]
                if position_flags[i] is not None and intc >= level_value:
                    stored_value = position_flags[i]
                    profit = intc - stored_value['intc']
                    print(f"Row {index}: CE Sell ({sell_level}) - time: {time}, intc: {intc}, Profit: {round(profit)}")
                    self.total  = self.total + round(profit)
                    position_flags[i] = None
                    instrument = stored_value['instrument']
                    closePrice = inth
                    signal = 'SELL'
                    orderType = i
                    self.createEntry(time, instrument, closePrice, signal, orderType)
                    

            # PE Sell Conditions
            for i, buy_level in enumerate(PEbuyLevel, start=len(CEbuyLevel)):
                sell_level = PEsellLevel[i - len(CEbuyLevel)]
                level_value = ppDict[sell_level]
                if position_flags[i] is not None and intc <= level_value:
                    stored_value = position_flags[i]
                    profit = stored_value['intc'] - intc
                    print(f"Row {index}: PE Sell ({sell_level}) - time: {time}, intc: {intc}, Profit: {round(profit)}")
                    self.total  = self.total + round(profit)
                    position_flags[i] = None
                    instrument = stored_value['instrument']
                    closePrice = inth
                    signal = 'SELL'
                    orderType = i
                    self.createEntry(time, instrument, closePrice, signal, orderType)
                    

            
            # CE Stop-Loss Conditions or PE Stop-Loss Conditions
            if self.stop_loss_points != 0 :
                #if intc > ppDict['R3_SL'] or intc < ppDict['S3_SL']:
                
                
                for i, price_details in position_flags.items():
                    sl_hit = False
                    if price_details is not None:
                        buy_price = price_details['intc']
                        instrument = price_details['instrument']
                        orderType = price_details['index']
                        closePrice = intc

                        if (instrument[-2:] == "CE" and intc < ppDict['R3_SL']) or (instrument[-2:] == "PE" and intc > ppDict['S3_SL']):
                            sl_hit = True
                        else:
                            continue


                        signal = 'SELL'
                        self.createEntry(time, instrument, closePrice, signal, orderType)

                        if instrument[-2:] == "CE":
                            instrType = 'CE'
                            if buy_price > intc:
                                loss = buy_price - intc
                                tmpStr = f' loss : {round(loss)}'
                                self.total  = self.total - round(loss)
                            else:
                                profit = intc - buy_price
                                tmpStr = f' profit : {round(profit)}'
                                self.total  = self.total + round(profit)
                        else:
                            instrType = 'PE'
                            if buy_price > intc:
                                profit = buy_price - intc
                                tmpStr = f' profit : {round(profit)}'
                                self.total  = self.total + round(profit)
                            else:
                                loss = intc - buy_price
                                tmpStr = f' loss : {round(loss)}'
                                self.total  = self.total - round(loss)


                        print(f"Row {index}: StopLoss Sell : ({instrType}) - time: {time}, intc: {intc}, {tmpStr}")
                        #do_not_transact_anymore = True
                        self.number_of_sl = self.number_of_sl + 1
                        position_flags[i] = None
            
        
        # Forced Sell at the End
        if self.forced_sell_enabled and new_order_time >= self.forced_sell_time and not do_not_transact_anymore:
            for i, price_details in position_flags.items():
                if price_details is not None:
                    buy_price = price_details['intc']
                    instrument = price_details['instrument']
                    orderType = price_details['index']
                    closePrice = intc
                    signal = 'SELL'
                    self.createEntry(time, instrument, closePrice, signal, orderType)

                    if instrument[-2:] == "CE":
                        instrType = 'CE'
                        if buy_price > intc:
                            loss = buy_price - intc
                            tmpStr = f' loss : {round(loss)}'
                            self.total  = self.total - round(loss)
                        else:
                            profit = intc - buy_price
                            tmpStr = f' profit : {round(profit)}'
                            self.total  = self.total + round(profit)
                    else:
                        instrType = 'PE'
                        if buy_price > intc:
                            profit = buy_price - intc
                            tmpStr = f' profit : {round(profit)}'
                            self.total  = self.total + round(profit)
                        else:
                            loss = intc - buy_price
                            tmpStr = f' loss : {round(loss)}'
                            self.total  = self.total - round(loss)


                    print(f"Row {index}: Forced Sell : ({instrType}) - time: {time}, intc: {intc}, {tmpStr}")


        print(f"Total Points secured : {self.total}\n")

        if self.total != 0:
            self.number_of_days_played = self.number_of_days_played + 1
            if self.total > 0:
                self.number_of_positive_days = self.number_of_positive_days + 1


        self.historyTotal  = self.historyTotal  + self.total
        print (f"Overall Total : {self.historyTotal} (played: {self.number_of_days_played} +ve: {self.number_of_positive_days} skip:{self.number_of_days_with_no_data} SL-C : {self.number_of_sl})\n\n")

                    

                    
        

   
    def calculate_pivot_levels(self, ohlc):
        open_price = float(ohlc['open'])
        high = float(ohlc['high'])
        low = float(ohlc['low'])
        close = float(ohlc['close'])
        
        # Calculate Pivot Point
        pivot = (high + close + low + open_price) / 4

        # Calculate Resistance Levels
        r1 = (2 * pivot) - low
        r2 = pivot + (r1 - ((2 * pivot) - high))
        r3 = high + 2.19 * (pivot - low)
        r3_sl = round(r3) + self.stop_loss_points
        #r1 = high
        #r2 = r1 + 74
        #r3 = r2 + 99
        #r3_sl = round(pivot - 25)

        # Calculate Support Levels
        
        #s1 = low
        #s2 = s1 - 74
        #s3 = s2 - 99
        s1 = (2 * pivot) - high
        s2 = pivot - (r1 - s1)
        s3 = low - 2.19 * (high - pivot)
        s3_sl = round(s3) - self.stop_loss_points
        #s3_sl = round(pivot + 25)

        '''
        pivot = low
          # Calculate Resistance Levels
        r1 = high
        r2 = r1 + 75
        r3 = r2 + 75
        r3_sl = low

        # Calculate Support Levels
        s1 = pivot - 75
        s2 = s1 - 75
        s3 = s2 - 75
        s3_sl = high
        '''
        returnVal = {
                'PP': round(pivot),
                'R1': round(r1),
                'R2': round(r2),
                'R3': round(r3),
                'R3_SL':r3_sl,
                'S1': round(s1),
                'S2': round(s2),
                'S3': round(s3),
                'S3_SL':s3_sl
            }
        

        return returnVal




            

    def createEntry(self, time, instrument, closePrice, signal, orderType):
        return

        url = "http://143.244.141.41/php/createDeepSeekEntry.php"  # Replace with your actual URL

        tickTime_str = time.strftime("%Y-%m-%d_%H:%M:%S")


        # 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")
            pass


    def fetch_all_data(self):
        
        
        symbols = {
            "NSE:Nifty 50": 30,
        }

        enable_back_testing = False
        enable_back_testing = True

        for interval in [1]:
            for symbol, days in symbols.items():
                time.sleep(1)
                start_time = datetime.now(self.ist).replace(hour=9, minute=15, second=0)
                end_time = datetime.now(self.ist).replace(hour=15, minute=30, second=0)

                if not enable_back_testing:
                    self.process_symbol_data(symbol, interval, start_time, end_time)
                else:
                    today_start_time = start_time
                    today_end_time = end_time
                    for day in range(0, days, 1):
                        start_time = today_start_time - timedelta(days=day)
                        end_time = today_end_time - timedelta(days=day)
                        self.process_symbol_data(symbol, interval, start_time, end_time)
                        time.sleep(1)



if __name__ == "__main__":
    client_details = ['GA0810', 'O9i8u7y6$$', '08101994', 'GA0810_API', 'ada14c5f73f182ca724b90c5dde1e72d']
    stock_fetcher = StockDataFetcher(client_details)
    stock_fetcher.login()
    stock_fetcher.fetch_all_data()
