"""
Cuchulufm is a series of wrappers for bottle, mako and others and construct a simple headless cms.

Copyright (C) 2023 Antonio de la Rosa Caballero

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
"""

from cuchulu.libraries.db.webmodel import PhangoField
from cuchulu.libraries.db import coreforms
from cuchulu.libraries.i18n import I18n
#from bs4 import BeautifulSoup
import bleach

class IntegerField(PhangoField):
    
    """Class that figure an integer sql type field.
    """
    
    def __init__(self, name, size=11, required=False):
        """
        Args:
            name (str): The name of field
            size (int): The size of the new field in database. By default 11.
            required (bool): Boolean for define if field is required or not
        """
        
        super(IntegerField, self).__init__(name, size, required)
        self.default_value=0
        self.type_sql='int({})'.format(self.size)
        
        self.default_value=0
        self.jtype='integer'
        self.jformat='int64'
        self.jexample='1'
    
    def check(self, value):
        
        """Method for check if value is integer

        Args:
            value (int): The value to check

        """
        
        self.error=False
        self.txt_error=''
        
        try:
        
            value=str(int(value))
        
            if value=="0" and self.required==True:
                self.txt_error="The value is zero"
                self.error=True
        except:
            
            value="0"
            self.txt_error="The value is zero"
            self.error=True
        
        return value
    
    def get_type_sql(self):
        
        """Method for return the sql code for this type

        """

        return 'INT('+str(self.size)+') NOT NULL DEFAULT "0"'
        
class BigIntegerField(IntegerField):
    
    """Class that figure an big integer sql type field.
    
    Only change the sql type with respect to IntegerField

    """
    
    def __init__(self, name, size=11, required=False):
    
        super().__init__(name, size, required)
        self.type_sql='bigint({})'.format(self.size)
    
    def get_type_sql(self):
        
        """Method for return the sql code for this type

        """

        return 'BIGINT('+str(self.size)+') NOT NULL DEFAULT "0"'
    

class FloatField(PhangoField):
    
    """Class that figure an float sql type field.

    Args:
        name (str): The name of new field
        size (int): The size of the new field in database. By default 11.
        required (bool): Boolean for define if 

    """
    
    def __init__(self, name, size=11, required=False):
        super(FloatField, self).__init__(name, size, required)
        
        self.error_default="The value is zero"
        self.default_value=0
        self.type_sql='float'.format(self.size)
    
        self.jtype='number'
        self.jformat='float'
    
    def check(self, value):
        
        """Method for check if value is integer

        Args:
            value (float): The value to check

        """
        
        self.error=False
        self.txt_error=''
        
        try:

            value=str(value)

            if value.find(',')!=-1:
                value=value.replace(',', '.')
            
            value=str(float(value))
            
            if value==0 and self.required==True:
                self.txt_error=self.error_default
                self.error=True
        except:
            
            value="0"
            self.txt_error=self.error_default
            self.error=True
        
        return value
    
    def get_type_sql(self):

        return 'FLOAT NOT NULL DEFAULT "0"'

class DecimalField(FloatField):
    """PhangoField field for Decimals fields."""
    
    def __init__(self, name, size=11, required=False):
    
        super().__init__(name, size, required)
        self.type_sql='decimal(20,2)'

        self.jtype='number'
    
    def get_type_sql(self):

        return 'DECIMAL(20, 2) NOT NULL DEFAULT "0"'

class DoubleField(FloatField):
    """PhangoField field for Double fields."""
    
    def __init__(self, name, size=11, required=False):
    
        super().__init__(name, size, required)
        self.type_sql='double'
    
        self.jtype='number'
        self.jformat='double'
    
    def get_type_sql(self):

        return 'DOUBLE NOT NULL DEFAULT "0"'

class CharField(PhangoField):
    """Simple alias for PhangoField"""
    
    pass

class TextField(PhangoField):
    """Class used for text fields
    
    Class used for text fields, use TEXT sql type for the this field.
    """
    
    def __init__(self, name, required=False):
        """Init TextField class different to standard PhangoField
        
        Args:
            name (str): The name of new field
            required (bool): Boolean for define if field is required or not
            
        Attributes: 
            set_default (str): Set if the value es NOT NULL or not
        """
        
        super().__init__(name, 11, required)
        
        self.type_sql='text'
        
        self.set_default='NOT NULL'
        
    def get_type_sql(self):
        
        """Method for return the sql code for this type

        """

        return 'TEXT '+self.set_default

class LongTextField(TextField):
    """Class used for long text fields (32 bits size, 4G)
    
    Class used for text fields, use LONGTEXT sql type for the this field.
    """
    
    def __init__(self, name, required=False):
        """Init TextField class different to standard PhangoField
        
        Args:
            name (str): The name of new field
            required (bool): Boolean for define if field is required or not
            
        Attributes: 
            set_default (str): Set if the value es NOT NULL or not
        """
        
        super().__init__(name, required)
        self.type_sql='longtext'
    
    def get_type_sql(self):
        
        """Method for return the sql code for this type

        """

        return 'LONGTEXT '+self.set_default

class HTMLField(TextField):
    """Class used for html fields
    
    Class used for html fields, use TEXT sql type for the this field because is children of TextField. In this method self.escape is used for convert " to &quot;
    """
    
    def __init__(self, name, required=False):
        """Init HTMLField class different to standard PhangoField
        
        Args:
            name (str): The name of new field
            required (bool): Boolean for define if field is required or not
        
        Attributes:
            trusted_tags (list): A list with safe tags.
        """
        
        super().__init__(name, required)
        self.trusted_tags=['b', 'strong']

    def check(self, value):
        """Check method for html values
        
        This check method use beautifulsoap for clean and format html code
        """
        
        # leach.clean('<p>"trial"</p><script></script>', tags=('p'))
        """
        soup=BeautifulSoup(value, features='html.parser')

        for tag in soup.findAll(True):
            
            if tag.name not in self.trusted_tags:
                tag.hidden=True

        value=soup.renderContents().decode('utf-8')
        
        if self.escape:
            
            return value.replace('"', '&quot;')
        else:
            
            return value

        """
        
        value=bleach.clean(value, tags=self.trusted_tags)
        
        if self.escape:
            
            return value.replace('"', '&quot;')
        else:
            
            return value
        
        
class ForeignKeyField(IntegerField):
    """Subclass of IntegerField for create Foreign keys
    
    A subclass of IntegerField used for create foreign keys in related tables. 
    """
    
    def __init__(self, name, related_table, size=11, required=False, identifier_field='id', named_field="id", select_fields=[]):
        """
        Args:
            name (str): Name of field
            related_table (WebModel): The table-model related with this foreign key
            size (int): The size of the new field in database. By default 11.
            required (bool): Boolean for define if field is required or not
            identifier_field (str): The Id field name from related table
            named_field (str): The field from related table used for identify the row seleted from related table
            select_fields (list): A series of fields names from related 
        """
        
        super(ForeignKeyField, self).__init__(name, size, required)
        
        self.table_id=related_table.name_field_id
    
        self.table_name=related_table.name
    
        self.related_model=related_table
    
        self.identifier_field=identifier_field
        
        self.named_field=named_field
        
        self.select_fields=select_fields
    
        self.foreignkey=True
        
        self.change_form(coreforms.SelectModelForm, [related_table, self.named_field, self.identifier_field])

        self.default_value=None

    def check(self, value):
        
        value=super().check(value)
        
        if value=='0' or value==0:
            value=None
            
        return value

    def get_type_sql(self):
        
        """Method for return the sql code for this type

        """

        return 'INT NULL'
        

class BooleanField(IntegerField):
    """Field for boolean values
    """
    
    def __init__(self, name, size=1):
        """
        Args:
            name (str): Name of field
            size (int): The size of the new field in database. By default 11.
        """
        
        required=False
        
        self.yes_text=_('Yes')
        self.no_text=_('No')
        
        super(IntegerField, self).__init__(name, size, required)
    
        self.default_error="Need 0 or 1 value"
        self.default_value=0
    
        self.type_sql='tinyint(1)'
        
        self.jtype='boolean'
        #self.jformat='0'
        self.jexample='0'
    
    def check(self, value):
        
        self.error=False
        self.txt_error=''
        
        try:
        
            value=int(value)
            
            if value<0 or value>1:
                self.txt_error=self.default_error
                self.error=True
                value=0

        except:
            
            self.error=True
            self.txt_error=self.default_error
            value=0

        value=str(value)

        return value
    
    def get_type_sql(self):
        
        """Method for return the sql code for this type

        """

        return 'BOOLEAN NOT NULL DEFAULT "0"'
    
    def show_formatted(self, value):
    
        value=int(value)
        
        if value==0:
            value=self.no_text
        else:
            value=self.yes_text
    
        return str(value)
