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) < 6:
            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) ))]



        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

        # take OHLC based on first 5 minutes
        ohlc = {
            'open': df.iloc[0]['into'],  # Open from the first entry
            'close': df.iloc[4]['intc'],  # Close from the 4th entry
            'high': df.iloc[:5]['inth'].max(),  # Highest value from the first 5 entries
            'low': df.iloc[:5]['intl'].min()  # Lowest value from the first 5 entries
        }


        ppDict = self.calculate_pivot_levels(ohlc)
        print(f"Pivot Levels : {ppDict}")


        df = df.iloc[90:].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
        PEbuyLevel = ['S1']
        PEsellLevel = [ 'S1_P' ]

        CEbuyLevel = ['R1']
        CEsellLevel = ['R1_P']

        # Track positions
        position_flags = {i: None for i in range(len(CEbuyLevel) + len(PEbuyLevel))}
        
        abs_allowed_diff = 7

        loss_count = 0
        allowed_losses = 13

        

        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'


            # CE Buy Conditions
            for i, level in enumerate(CEbuyLevel):
                level_value = ppDict[level]
                if level_value <= inth <= level_value + abs_allowed_diff and position_flags[i] is None and loss_count < allowed_losses:
                    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 = intc
                    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 <= intl <= level_value and position_flags[i] is None and loss_count < allowed_losses:
                    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 = intc
                    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)}")
                    if profit < 0:
                        loss_count = loss_count + 1
                    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)}")
                    if profit < 0:
                        loss_count = loss_count + 1
                    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 :

                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)
                                loss_count = loss_count + 1
                            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)
                                loss_count = loss_count + 1


                        print(f"Row {index}: StopLoss Sell : ({instrType}) - time: {time}, intc: {intc}, {tmpStr}")
                        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 :
            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 = high
        r2 = r1 + 74
        r3 = r2 + 74
        r1_p = r1 + 74
        r3_sl = round(r1 - 25)

        # Calculate Support Levels
        s1 = low
        s2 = s1 - 74
        s3 = s2 - 74
        s1_p = s1 - 74
        s3_sl = round(s1 + 25)

        returnVal = {
                'PP': round(pivot),
                'R1': round(r1),
                'R2': round(r2),
                'R3': round(r3),
                'R1_P': round(r1_p),
                'R3_SL':r3_sl,
                'S1': round(s1),
                'S2': round(s2),
                'S3': round(s3),
                'S1_P': round(s1_p),
                '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": 90,
        }

        #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()
