import os
import datetime
import calendar
from PIL import Image
from django.db import models
from django.db.models import Sum
from django.db.models.signals import post_save
from django.contrib.sitemaps import Sitemap
from django.dispatch import receiver
from django.urls import reverse
from django.apps import apps
from django.utils.html import format_html
from django.utils import timezone
from django.conf import settings
from django.template.defaultfilters import slugify

from media.models import LuxImage, LuxImageSize
from media.utils import resize_image
from utils.util import render_images, render_products, parse_video, markdown_to_html


def get_upload_path(self, filename):
    return "images/products/%s" % (filename)


class Ticker(models.Model):
    symbol = models.CharField(max_length=9)
    name = models.CharField(max_length=243, blank=True, null=True)
    current_price = models.CharField(max_length=243, blank=True, null=True)
    price_date = models.DateTimeField(null=True)

    def __str__(self):
        return str(self.symbol)


class Price(models.Model):
    symbol = models.ForeignKey(Ticker, null=True, on_delete=models.SET_NULL)
    price = models.CharField(max_length=243, blank=True, null=True)
    price_date = models.DateTimeField()


class OptionsTrade(models.Model):
    date = models.DateTimeField()
    symbol = models.ForeignKey(Ticker, null=True, on_delete=models.SET_NULL)
    TRANSACTION_CODE = (
        ('Trade', 'Trade'),
    )
    transaction_code = models.CharField(choices=TRANSACTION_CODE, max_length=25)
    TRANSACTION_SUBCODE = (
        ('Buy to Open', 'Buy to Open'),
        ('Sell to Open', 'Sell to Open'),
        ('Sell to Close', 'Sell to Close'),
    )
    transaction_subcode = models.CharField(choices=TRANSACTION_SUBCODE, max_length=25)
    BUY_SELL = (
        ('Buy', 'Buy'),
        ('Sell', 'Sell'),
    )
    buy_sell = models.CharField(choices=BUY_SELL, max_length=4)
    OPEN_CLOSE = (
        ('Open', 'Open'),
        ('Close', 'Close'),
    )
    open_close = models.CharField(choices=OPEN_CLOSE, max_length=5)
    quantity = models.FloatField()
    expiration_date = models.DateTimeField()
    strike = models.FloatField()
    CALL_PUT = (
        ('C', 'Call'),
        ('P', 'Put'),
    )
    call_put = models.CharField(choices=CALL_PUT, max_length=4)
    price = models.FloatField()
    fees = models.FloatField()
    amount = models.FloatField()
    description = models.TextField(blank=True)

    def __str__(self):
        return str(self.symbol)

    def get_profit_by_symbol(self,t):
        buy_amount = 0
        sell_amount = 0
        for o in OptionsTrade.objects.filter(symbol__symbol=t).filter(buy_sell="Sell"):
            sell_amount+=o.amount+o.fees
        for o in OptionsTrade.objects.filter(symbol__symbol=t).filter(buy_sell="Buy"):
            buy_amount+=o.amount+o.fees
        return buy_amount+sell_amount

    def get_profit(self):
        buy_amount = 0
        sell_amount = 0
        for o in OptionsTrade.objects.filter(buy_sell="Sell"):
            sell_amount+=o.amount+o.fees
        for o in OptionsTrade.objects.filter(buy_sell="Buy"):
            buy_amount+=o.amount+o.fees
        return buy_amount+sell_amount


class LuxTradeStatsManager(models.Manager):

    def get_month_pl(self, month=timezone.now().month):
        last_day = calendar.monthrange(timezone.now().year, month)[1]
        start_date = datetime.date(timezone.now().year, month, 1)
        end_date = datetime.date(timezone.now().year, month, last_day)
        return self.filter(close_date__range=(start_date, end_date)).aggregate(Sum('pl'))

    def get_year_pl(self, year=timezone.now().year):
        start_date = datetime.date(year, 1, 1)
        end_date = datetime.date(year, 12, 31)
        return self.filter(close_date__range=(start_date, end_date)).aggregate(Sum('pl'))


class LuxTrade(models.Model):
    symbol = models.CharField(max_length=256)
    date = models.DateTimeField(auto_now_add=True)
    close_date = models.DateTimeField(null=True, blank=True)
    open_date = models.DateTimeField(null=True, blank=True)
    entry_price = models.FloatField()
    stop_price = models.FloatField()
    target_price = models.FloatField()
    close_price = models.FloatField(null=True, blank=True)
    shares = models.FloatField()
    STATUS = (
        (0, 'Open'),
        (1, 'Closed'),
        (2, 'Watching'),
    )
    status = models.IntegerField(choices=STATUS, default=2)
    notes = models.TextField(null=True, blank=True)
    notes_html = models.TextField(null=True, blank=True)
    is_wanderer = models.BooleanField(default=True)
    pl = models.FloatField(null=True)
    
    class Meta:
        ordering = ('status', '-open_date',)
        get_latest_by = 'open_date'

    def __str__(self):
        return str(self.symbol)

    objects = models.Manager()  # The default manager.
    stats = LuxTradeStatsManager()

    def get_absolute_url(self):
        return reverse('luxtrade:detail', kwargs={"pk": self.pk})

    @property
    def risk_reward(self):
        if self.stop_price > self.entry_price:
            return "No risk"
        else: 
            return round((self.entry_price - self.stop_price)/(self.target_price-self.entry_price),2)

    @property
    def goal_dollars(self):
        return round((self.target_price*self.shares)-(self.entry_price*self.shares), 2)
    
    @property
    def goal_percent(self):
        return round((((self.target_price*self.shares)-(self.entry_price*self.shares))/self.amount_invested)*100, 2)

    @property
    def risk_dollars(self):
        if self.stop_price > self.entry_price:
            return 0
        else:
            return round((self.entry_price-self.stop_price)*self.shares, 2)
    
    @property
    def amount_invested(self):
        return round(self.entry_price * self.shares, 2)
    
    @property
    def realized_dollars(self):
        return round((self.close_price*self.shares)-(self.entry_price*self.shares), 2)
    
    @property
    def realized_percent(self):
        return round((self.realized_dollars/self.amount_invested)*100, 2)
 
    def save(self, *args, **kwargs):
        if self.status == 0 and not self.open_date:
            self.open_date = timezone.now()
        if self.status == 1 and not self.close_date:
            self.close_date = timezone.now()
        if self.status == 1 and not self.pl:
            self.pl = round((self.close_price*self.shares)-(self.entry_price*self.shares), 2)
        if self.notes:
            self.notes_html = markdown_to_html(self.notes)
        super(LuxTrade, self).save()

    
class LuxOptionsTradeStatsManager(models.Manager):

    def get_month_pl(self, month=timezone.now().month):
        last_day = calendar.monthrange(timezone.now().year, month)[1]
        start_date = datetime.date(timezone.now().year, month, 1)
        end_date = datetime.date(timezone.now().year, month, last_day)
        return self.filter(close_date__range=(start_date, end_date)).aggregate(Sum('pl'))

    def get_year_pl(self, year=timezone.now().year):
        start_date = datetime.date(year, 1, 1)
        end_date = datetime.date(year, 12, 31)
        return self.filter(close_date__range=(start_date, end_date)).aggregate(Sum('pl'))


class LuxOptionsTrade(models.Model):
    symbol = models.CharField(max_length=256)
    date = models.DateTimeField(auto_now_add=True)
    close_date = models.DateTimeField(null=True, blank=True)
    open_date = models.DateTimeField(null=True, blank=True)
    entry_price = models.FloatField()
    stop_price = models.FloatField()
    target_price = models.FloatField()
    strike_price = models.FloatField()
    CALL_PUT = (
        (0, 'Call'),
        (1, 'Put'),
    )
    call_put = models.IntegerField(choices=CALL_PUT, default=2)
    contract_price = models.FloatField()
    contract_close_price = models.FloatField(null=True, blank=True)
    number_contracts = models.FloatField()
    delta = models.FloatField()
    expiration_date = models.DateField()

    fees = models.FloatField(blank=True)
    STATUS = (
        (0, 'Open'),
        (1, 'Closed'),
        (2, 'Watching'),
    )
    status = models.IntegerField(choices=STATUS, default=2)
    notes = models.TextField(null=True, blank=True)
    pl = models.FloatField(null=True, blank=True)

    class Meta:
        ordering = ('-open_date',)
        get_latest_by = 'open_date'

    objects = models.Manager()  # The default manager.
    stats = LuxOptionsTradeStatsManager()

    def __str__(self):
        return str(self.symbol)

    def get_absolute_url(self):
        return reverse('luxtrade:optiondetail', kwargs={"pk": self.pk})

    @property
    def risk_per_contract(self):
        if self.call_put == 0:
            return round(((self.entry_price-self.stop_price)*self.delta/self.contract_price)*self.contract_price*100, 2)
        else:
            return round(((self.stop_price-self.entry_price)*self.delta/self.contract_price)*self.contract_price*100, 2)

    @property
    def risk_total(self):
        return round(self.risk_per_contract*self.number_contracts, 2)

    @property
    def risk_reward(self):
        return round((self.target_price-self.entry_price)/(self.entry_price-self.stop_price), 2);

    @property
    def amount_invested(self):
        return round(((self.contract_price * self.number_contracts)*100)+self.fees, 2)
    
    @property
    def profit_goal(self):
        if self.call_put == 0:
            return round((((self.target_price-(self.strike_price+self.contract_price))*100)*self.number_contracts)-self.fees, 2)
        else: 
            return round((((self.strike_price-self.target_price)*self.number_contracts)*100)-self.fees, 2)
    
    @property
    def days_until_expiration(self):
        td = self.expiration_date - datetime.date.today()
        return td.days

    @property
    def realized_dollars(self):
        if self.call_put == 0:
            return round((((self.contract_close_price*self.number_contracts)*100) - ((self.contract_price*self.number_contracts)*100)-self.fees), 2)
        else:
            return round((((self.contract_price*self.number_contracts)*100)-((self.contract_close_price*self.number_contracts)*100)-self.fees), 2)

    @property
    def trade_risk_percent(self):
        return round((self.risk_total/self.amount_invested)*100, 0);
    
    @property
    def portfolio_risk_percent(self):
        return round((self.risk_total/10000)*100, 0);


    @property
    def realized_percent(self):
        if self.call_put == 0:
            return round((self.realized_dollars/self.amount_invested)*100, 2)
        else:
            return round((self.pl/self.amount_invested)*100, 2)

    def save(self, *args, **kwargs):
        if self.status == 0 and not self.open_date:
            self.open_date = timezone.now()
        if self.number_contracts < 10:
            self.fees = self.number_contracts + (.14*self.number_contracts)
        else:
            self.fees = 10 + (.14*self.number_contracts)
        if self.status == 1 and not self.close_date:
            self.close_date = timezone.now()
        if self.status == 1 and not self.pl:
            if self.call_put == 0:
                self.pl = round(((self.contract_close_price*self.number_contracts)*100) - ((self.contract_price*self.number_contracts)*100), 2)
            else:
                self.pl = round(((self.contract_close_price*self.number_contracts)*100) - ((self.contract_price*self.number_contracts)*100), 2)

        if self.notes:
            self.notes_html = markdown_to_html(self.notes)
        super(LuxOptionsTrade, self).save()


class TradeJrnl(models.Model):
    date = models.DateTimeField(auto_now_add=True)
    body_markdown = models.TextField(blank=True)
    body_html = models.TextField(null=True, blank=True)
    
    def __str__(self):
        return str(self.date)

    @property
    def get_previous_admin_url(self):
        n = self.get_previous_by_read_date()
        return reverse('admin:%s_%s_change' % (self._meta.app_label,  self._meta.model_name),  args=[n.id])

    @property
    def get_next_admin_url(self):
        model = apps.get_model(app_label=self._meta.app_label, model_name=self._meta.model_name)
        try:
            return reverse('admin:%s_%s_change' % (self._meta.app_label,  self._meta.model_name),  args=[self.get_next_by_read_date().pk])
        except model.DoesNotExist:
            return ''


    def save(self, *args, **kwargs):
        md = render_images(self.body_markdown)
        self.body_html = markdown_to_html(md)
        super(TradeJrnl, self).save()


class LuxOptionsPurchaseStatsManager(models.Manager):

    def get_month_pl(self, month=timezone.now().month):
        last_day = calendar.monthrange(timezone.now().year, month)[1]
        start_date = datetime.date(timezone.now().year, month, 1)
        end_date = datetime.date(timezone.now().year, month, last_day)
        return self.filter(close_date__range=(start_date, end_date)).aggregate(Sum('pl'))

    def get_year_pl(self, year=timezone.now().year):
        start_date = datetime.date(year, 1, 1)
        end_date = datetime.date(year, 12, 31)
        return self.filter(close_date__range=(start_date, end_date)).aggregate(Sum('pl'))


class LuxOptionPurchase(models.Model):
    symbol = models.CharField(max_length=256)
    open_date = models.DateTimeField(auto_now_add=True)
    close_date = models.DateTimeField(null=True, blank=True)
    pl = models.FloatField(null=True, blank=True)
    STATUS = (
        (0, 'Open'),
        (1, 'Closed'),
    )
    status = models.IntegerField(choices=STATUS, default=0)

    class Meta:
        ordering = ('-open_date', 'status')
        get_latest_by = 'open_date'

    objects = models.Manager()  # The default manager.
    stats = LuxOptionsPurchaseStatsManager()

    def __str__(self):
        return str(self.symbol)

    def get_absolute_url(self):
        return reverse('luxtrade:option-update', kwargs={"pk": self.pk})

    def get_contract_count(self):
        return self.luxoptioncontract_set.count()

    @property
    def profit_loss(self):
        total = float(0)
        for option in self.luxoptioncontract_set.all():
            if option.contract_close_price:
                pl = option.contract_close_price - option.contract_open_price
            else:
                pl = 0
            total = total + pl
        return round((total*100),2)

    @property
    def stop_price(self):
        return (self.contract_price*.75)

    @property
    def total_invested(self):
        return self.get_contract_count()*(self.luxoptioncontract_set.first().contract_open_price*100)

    @property
    def trade_risk(self):
        return self.total_invested*.25

    @property
    def portfolio_risk(self):
        return (self.trade_risk/10000)*100

    @property
    def sell_half_at(self):
        return self.luxoptioncontract_set.first().contract_open_price*1.25

    @property
    def contract_price(self):
        return self.luxoptioncontract_set.first().contract_open_price

    def save(self, *args, **kwargs):
        if self.status == 1 and not self.close_date:
            self.close_date = timezone.now()
        if self.status == 1:
            print("updating pl")
            self.pl = self.profit_loss
        super(LuxOptionPurchase, self).save()


class LuxOptionContract(models.Model):
    symbol = models.CharField(max_length=256)
    strike_price = models.FloatField()
    expiration_date = models.DateField()
    contract_open_price = models.FloatField()
    contract_close_price = models.FloatField(null=True, blank=True)
    CALL_PUT = (
        (0, 'Calls'),
        (1, 'Puts'),
    )
    call_put = models.IntegerField(choices=CALL_PUT, default=0)
    options_purchase = models.ForeignKey(LuxOptionPurchase, null=True, on_delete=models.SET_NULL)

    def __str__(self):
        return "%s - %s %s" %(self.symbol, round(self.strike_price), self.get_call_put_display())