# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

import datetime

from sql import Literal
from sql.aggregate import Max, Sum
from sql.functions import Extract, CharLength

from trytond.i18n import gettext
from trytond.model import ModelView, ModelSQL, fields, Unique
from trytond.wizard import Wizard, StateView, StateAction, Button
from trytond.pyson import Eval, PYSONEncoder, Date
from trytond.transaction import Transaction
from trytond.pool import Pool

from .exceptions import DurationValidationError

__all__ = ['Line', 'EnterLinesStart', 'EnterLines',
    'HoursEmployee', 'HoursEmployeeContext',
    'HoursEmployeeWeekly', 'HoursEmployeeMonthly']


class Line(ModelSQL, ModelView):
    'Timesheet Line'
    __name__ = 'timesheet.line'
    company = fields.Many2One('company.company', 'Company', required=True,
        help="The company on which the time is spent.")
    employee = fields.Many2One('company.employee', 'Employee', required=True,
        select=True, domain=[
            ('company', '=', Eval('company', -1)),
            ['OR',
                ('start_date', '=', None),
                ('start_date', '<=', Eval('date')),
                ],
            ['OR',
                ('end_date', '=', None),
                ('end_date', '>=', Eval('date')),
                ],
            ],
        depends=['company', 'date'],
        help="The employee who spends the time.")
    date = fields.Date('Date', required=True, select=True,
        help="When the time is spent.")
    duration = fields.TimeDelta('Duration', 'company_work_time', required=True)
    work = fields.Many2One('timesheet.work', 'Work',
        required=True, select=True, domain=[
            ('company', '=', Eval('company', -1)),
            ['OR',
                ('timesheet_start_date', '=', None),
                ('timesheet_start_date', '<=', Eval('date')),
                ],
            ['OR',
                ('timesheet_end_date', '=', None),
                ('timesheet_end_date', '>=', Eval('date')),
                ],
            ],
        depends=['date', 'company'],
        help="The work on which the time is spent.")
    description = fields.Char('Description',
        help="Additional description of the work done.")
    uuid = fields.Char("UUID", readonly=True)

    @classmethod
    def __setup__(cls):
        super(Line, cls).__setup__()
        t = cls.__table__()
        cls._sql_constraints = [
            ('uuid_unique', Unique(t, t.uuid),
                'timesheet.msg_line_uuid_unique'),
            ]
        cls._order.insert(0, ('date', 'DESC'))

    @classmethod
    def __register__(cls, module_name):
        cursor = Transaction().connection.cursor()
        table = cls.__table_handler__(module_name)
        sql_table = cls.__table__()
        pool = Pool()
        Work = pool.get('timesheet.work')
        work = Work.__table__()

        created_company = not table.column_exist('company')

        super(Line, cls).__register__(module_name)

        # Migration from 3.4: new company field
        if created_company:
            # Don't use FROM because SQLite nor MySQL support it.
            cursor.execute(*sql_table.update(
                    [sql_table.company], [work.select(work.company,
                            where=work.id == sql_table.work)]))
        # Migration from 3.4: change hours into timedelta duration
        if table.column_exist('hours'):
            table.drop_constraint('check_move_hours_pos')
            cursor.execute(*sql_table.select(
                    sql_table.id, sql_table.hours))
            for id_, hours in cursor.fetchall():
                duration = datetime.timedelta(hours=hours)
                cursor.execute(*sql_table.update(
                        [sql_table.duration],
                        [duration],
                        where=sql_table.id == id_))
            table.drop_column('hours')

    @staticmethod
    def default_company():
        return Transaction().context.get('company')

    @staticmethod
    def default_employee():
        User = Pool().get('res.user')
        employee_id = None
        if Transaction().context.get('employee'):
            employee_id = Transaction().context['employee']
        else:
            user = User(Transaction().user)
            if user.employee:
                employee_id = user.employee.id
        if employee_id:
            return employee_id

    @staticmethod
    def default_date():
        Date_ = Pool().get('ir.date')
        return Transaction().context.get('date') or Date_.today()

    @classmethod
    def validate(cls, lines):
        super(Line, cls).validate(lines)
        for line in lines:
            line.check_duration()

    def check_duration(self):
        if self.duration < datetime.timedelta():
            raise DurationValidationError(
                gettext('timesheet.msg_line_duration_positive',
                    line=self.rec_name))

    @classmethod
    def copy(cls, lines, default=None):
        if default is not None:
            default = default.copy()
        else:
            default = {}
        default.setdefault('uuid')
        return super().copy(lines, default=default)

    @property
    def hours(self):
        return self.duration.total_seconds() / 60 / 60

    def to_json(self):
        return {
            'id': self.id,
            'work': self.work.id,
            'work.name': self.work.rec_name,
            'duration': self.duration.total_seconds(),
            'description': self.description,
            'uuid': self.uuid,
            }


class EnterLinesStart(ModelView):
    'Enter Lines'
    __name__ = 'timesheet.line.enter.start'
    employee = fields.Many2One('company.employee', 'Employee', required=True,
        domain=[
            ('company', '=', Eval('context', {}).get('company', -1)),
            ['OR',
                ('start_date', '=', None),
                ('start_date', '<=', Eval('date')),
                ],
            ['OR',
                ('end_date', '=', None),
                ('end_date', '>=', Eval('date')),
                ],
            ],
        depends=['date'],
        help="The employee who spends the time.")
    date = fields.Date('Date', required=True,
        help="When the time is spent.")

    @staticmethod
    def default_employee():
        Line = Pool().get('timesheet.line')
        return Line.default_employee()

    @staticmethod
    def default_date():
        Line = Pool().get('timesheet.line')
        return Line.default_date()


class EnterLines(Wizard):
    'Enter Lines'
    __name__ = 'timesheet.line.enter'
    start = StateView('timesheet.line.enter.start',
        'timesheet.line_enter_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Enter', 'enter', 'tryton-ok', default=True),
            ])
    enter = StateAction('timesheet.act_line_form')

    def do_enter(self, action):
        date = self.start.date
        date = Date(date.year, date.month, date.day)
        action['pyson_domain'] = PYSONEncoder().encode([
                ('employee', '=', self.start.employee.id),
                ('company', '=', self.start.employee.company.id),
                ('date', '=', date),
                ])
        action['pyson_context'] = PYSONEncoder().encode({
                'employee': self.start.employee.id,
                'company': self.start.employee.company.id,
                'date': date,
                })
        action['name'] += " - " + self.start.employee.rec_name
        return action, {}

    def transition_enter(self):
        return 'end'


class HoursEmployee(ModelSQL, ModelView):
    'Hours per Employee'
    __name__ = 'timesheet.hours_employee'
    employee = fields.Many2One('company.employee', 'Employee')
    duration = fields.TimeDelta('Duration', 'company_work_time')

    @staticmethod
    def table_query():
        pool = Pool()
        Line = pool.get('timesheet.line')
        line = Line.__table__()
        where = Literal(True)
        if Transaction().context.get('start_date'):
            where &= line.date >= Transaction().context['start_date']
        if Transaction().context.get('end_date'):
            where &= line.date <= Transaction().context['end_date']
        return line.select(
            line.employee.as_('id'),
            Max(line.create_uid).as_('create_uid'),
            Max(line.create_date).as_('create_date'),
            Max(line.write_uid).as_('write_uid'),
            Max(line.write_date).as_('write_date'),
            line.employee,
            Sum(line.duration).as_('duration'),
            where=where,
            group_by=line.employee)


class HoursEmployeeContext(ModelView):
    'Hours per Employee Context'
    __name__ = 'timesheet.hours_employee.context'
    start_date = fields.Date('Start Date')
    end_date = fields.Date('End Date')


class HoursEmployeeWeekly(ModelSQL, ModelView):
    'Hours per Employee per Week'
    __name__ = 'timesheet.hours_employee_weekly'
    year = fields.Char('Year')
    week_internal = fields.Char('Week')
    week = fields.Function(fields.Char('Week'),
        'get_week', searcher='search_week')
    employee = fields.Many2One('company.employee', 'Employee')
    duration = fields.TimeDelta('Duration', 'company_work_time')

    @classmethod
    def __setup__(cls):
        super(HoursEmployeeWeekly, cls).__setup__()
        cls._order.insert(0, ('year', 'DESC'))
        cls._order.insert(1, ('week', 'DESC'))
        cls._order.insert(2, ('employee', 'ASC'))

    @classmethod
    def table_query(cls):
        pool = Pool()
        Line = pool.get('timesheet.line')
        line = Line.__table__()
        type_name = cls.year.sql_type().base
        year_column = Extract('YEAR', line.date).cast(type_name).as_('year')
        type_name = cls.week_internal.sql_type().base
        week_column = Extract('WEEK', line.date).cast(type_name).as_(
            'week_internal')
        return line.select(
            Max(Extract('WEEK', line.date)
                + Extract('YEAR', line.date) * 100
                + line.employee * 1000000).as_('id'),
            Max(line.create_uid).as_('create_uid'),
            Max(line.create_date).as_('create_date'),
            Max(line.write_uid).as_('write_uid'),
            Max(line.write_date).as_('write_date'),
            year_column,
            week_column,
            line.employee,
            Sum(line.duration).as_('duration'),
            group_by=(year_column, week_column, line.employee))

    def get_week(self, name):
        return '%02i' % int(self.week_internal)

    @classmethod
    def search_week(cls, name, domain):
        return [('week_internal',) + tuple(domain[1:])]

    @classmethod
    def order_week(cls, tables):
        table, _ = tables[None]
        return [CharLength(table.week_internal), table.week_internal]


class HoursEmployeeMonthly(ModelSQL, ModelView):
    'Hours per Employee per Month'
    __name__ = 'timesheet.hours_employee_monthly'
    year = fields.Char('Year')
    month_internal = fields.Char('Month')
    month = fields.Function(fields.Char('Month'),
        'get_month', searcher='search_month')
    employee = fields.Many2One('company.employee', 'Employee')
    duration = fields.TimeDelta('Duration', 'company_work_time')

    @classmethod
    def __setup__(cls):
        super(HoursEmployeeMonthly, cls).__setup__()
        cls._order.insert(0, ('year', 'DESC'))
        cls._order.insert(1, ('month', 'DESC'))
        cls._order.insert(2, ('employee', 'ASC'))

    @classmethod
    def table_query(cls):
        pool = Pool()
        Line = pool.get('timesheet.line')
        line = Line.__table__()
        type_name = cls.year.sql_type().base
        year_column = Extract('YEAR', line.date).cast(type_name).as_('year')
        type_name = cls.month_internal.sql_type().base
        month_column = Extract('MONTH', line.date).cast(type_name).as_(
            'month_internal')
        return line.select(
            Max(Extract('MONTH', line.date)
                + Extract('YEAR', line.date) * 100
                + line.employee * 1000000).as_('id'),
            Max(line.create_uid).as_('create_uid'),
            Max(line.create_date).as_('create_date'),
            Max(line.write_uid).as_('write_uid'),
            Max(line.write_date).as_('write_date'),
            year_column,
            month_column,
            line.employee,
            Sum(line.duration).as_('duration'),
            group_by=(year_column, month_column, line.employee))

    def get_month(self, name):
        return '%02i' % int(self.month_internal)

    @classmethod
    def search_month(cls, name, domain):
        return [('month_internal',) + tuple(domain[1:])]

    @classmethod
    def order_month(cls, tables):
        table, _ = tables[None]
        return [CharLength(table.month_internal), table.month_internal]
