# -*- coding: utf-8 -*-
from datetime import date, timedelta

from odoo import api, fields, models
from odoo.exceptions import ValidationError


def monday_of(day):
    """Return the Monday on or before the given date."""
    return day - timedelta(days=day.weekday())


class TimesheetV2WeekPlan(models.Model):
    """One employee's planned work for a calendar week (Mon–Sun)."""

    _name = 'timesheet.v2.week.plan'
    _description = 'Weekly Timesheet Plan'
    _order = 'week_start desc, employee_id'
    _rec_name = 'display_name'

    employee_id = fields.Many2one(
        comodel_name='hr.employee',
        string='Employee',
        required=True,
        index=True,
        ondelete='cascade',
    )
    week_start = fields.Date(
        string='Week Start',
        required=True,
        index=True,
        default=lambda self: monday_of(date.today()),
    )
    week_end = fields.Date(
        string='Week End',
        compute='_compute_week_end',
        store=True,
    )
    display_name = fields.Char(compute='_compute_display_name', store=True)
    line_ids = fields.One2many(
        comodel_name='timesheet.v2.week.plan.line',
        inverse_name='plan_id',
        string='This Week To-Dos',
    )
    total_planned_hours = fields.Float(
        string='Planned Hours',
        compute='_compute_planned_totals',
        store=True,
    )
    total_actual_hours = fields.Float(
        string='Logged Hours',
        compute='_compute_actual_totals',
    )
    total_done = fields.Integer(
        string='Done Items',
        compute='_compute_planned_totals',
        store=True,
    )
    total_items = fields.Integer(
        string='To-Do Items',
        compute='_compute_planned_totals',
        store=True,
    )
    previous_plan_id = fields.Many2one(
        comodel_name='timesheet.v2.week.plan',
        string='Previous Week Plan',
        compute='_compute_previous_plan',
        store=True,
    )
    previous_line_ids = fields.One2many(
        related='previous_plan_id.line_ids',
        string='Previous Week To-Dos',
        readonly=True,
    )
    previous_total_planned = fields.Float(
        related='previous_plan_id.total_planned_hours',
        string='Previous Planned Hours',
    )
    previous_total_actual = fields.Float(
        related='previous_plan_id.total_actual_hours',
        string='Previous Logged Hours',
    )
    company_id = fields.Many2one(
        comodel_name='res.company',
        related='employee_id.company_id',
        store=True,
    )

    _sql_constraints = [
        (
            'employee_week_unique',
            'unique(employee_id, week_start)',
            'Each employee can only have one plan per week.',
        ),
    ]

    @api.depends('week_start')
    def _compute_week_end(self):
        for plan in self:
            plan.week_end = plan.week_start + timedelta(days=6) if plan.week_start else False

    @api.depends('employee_id', 'week_start', 'week_end')
    def _compute_display_name(self):
        for plan in self:
            if plan.employee_id and plan.week_start and plan.week_end:
                plan.display_name = '%s — %s to %s' % (
                    plan.employee_id.name,
                    plan.week_start,
                    plan.week_end,
                )
            else:
                plan.display_name = 'Weekly Plan'

    @api.depends('line_ids.planned_hours', 'line_ids.is_done')
    def _compute_planned_totals(self):
        for plan in self:
            plan.total_planned_hours = round(sum(plan.line_ids.mapped('planned_hours')), 2)
            plan.total_items = len(plan.line_ids)
            plan.total_done = len(plan.line_ids.filtered('is_done'))

    @api.depends('employee_id', 'week_start', 'week_end', 'line_ids')
    def _compute_actual_totals(self):
        AnalyticLine = self.env['account.analytic.line']
        for plan in self:
            if plan.employee_id and plan.week_start and plan.week_end:
                lines = AnalyticLine.search([
                    ('employee_id', '=', plan.employee_id.id),
                    ('date', '>=', plan.week_start),
                    ('date', '<=', plan.week_end),
                    ('project_id', '!=', False),
                ])
                plan.total_actual_hours = round(sum(lines.mapped('unit_amount')), 2)
            else:
                plan.total_actual_hours = 0.0

    @api.depends('employee_id', 'week_start')
    def _compute_previous_plan(self):
        for plan in self:
            previous = False
            if plan.employee_id and plan.week_start:
                prev_start = plan.week_start - timedelta(days=7)
                previous = self.search([
                    ('employee_id', '=', plan.employee_id.id),
                    ('week_start', '=', prev_start),
                ], limit=1)
            plan.previous_plan_id = previous

    @api.constrains('week_start')
    def _check_week_start_is_monday(self):
        for plan in self:
            if plan.week_start and plan.week_start.weekday() != 0:
                raise ValidationError('Week start must be a Monday.')

    @api.model
    def get_or_create_week(self, employee, week_start=None):
        """Return the plan for an employee/week, creating it if needed."""
        if not employee:
            return self.browse()
        if not week_start:
            week_start = monday_of(fields.Date.context_today(self))
        else:
            week_start = monday_of(fields.Date.to_date(week_start))
        plan = self.search([
            ('employee_id', '=', employee.id),
            ('week_start', '=', week_start),
        ], limit=1)
        if not plan:
            plan = self.create({
                'employee_id': employee.id,
                'week_start': week_start,
            })
        return plan

    @api.model
    def default_employee(self):
        return self.env['hr.employee'].search(
            [('user_id', '=', self.env.uid)], limit=1,
        )

    def action_open_my_timesheet(self):
        self.ensure_one()
        action = self.env.ref('jb_timesheet_v2.action_timesheet_v2_my_timesheet').read()[0]
        action['context'] = dict(self.env.context, **{
            'search_default_filter_my_entries': 1,
            'search_default_group_date_day': 0,
        })
        return action

    def action_prev_week(self):
        self.ensure_one()
        return self._action_open_week(self.week_start - timedelta(days=7))

    def action_next_week(self):
        self.ensure_one()
        return self._action_open_week(self.week_start + timedelta(days=7))

    def action_this_week(self):
        self.ensure_one()
        return self._action_open_week(monday_of(date.today()))

    def _action_open_week(self, week_start):
        self.ensure_one()
        plan = self.get_or_create_week(self.employee_id, week_start)
        return {
            'type': 'ir.actions.act_window',
            'name': 'My Dashboard',
            'res_model': 'timesheet.v2.week.plan',
            'view_mode': 'form',
            'res_id': plan.id,
            'target': 'current',
        }


class TimesheetV2WeekPlanLine(models.Model):
    """Single to-do on a weekly plan — linked to a client with estimated hours."""

    _name = 'timesheet.v2.week.plan.line'
    _description = 'Weekly Plan To-Do'
    _order = 'sequence, id'

    plan_id = fields.Many2one(
        comodel_name='timesheet.v2.week.plan',
        string='Weekly Plan',
        required=True,
        ondelete='cascade',
        index=True,
    )
    sequence = fields.Integer(default=10)
    name = fields.Char(
        string='To-Do',
        required=True,
        help='What you plan to work on this week.',
    )
    partner_id = fields.Many2one(
        comodel_name='res.partner',
        string='Client',
        index=True,
    )
    project_id = fields.Many2one(
        comodel_name='project.project',
        string='Project',
        domain="[('partner_id', '=', partner_id)]",
    )
    planned_hours = fields.Float(
        string='Planned Hrs',
        default=0.0,
    )
    is_done = fields.Boolean(
        string='Done',
        default=False,
    )
    actual_hours = fields.Float(
        string='Logged Hrs',
        compute='_compute_actual_hours',
    )
    variance_hours = fields.Float(
        string='Variance',
        compute='_compute_actual_hours',
        help='Logged hours minus planned hours.',
    )
    employee_id = fields.Many2one(
        related='plan_id.employee_id',
        store=True,
        readonly=True,
    )
    week_start = fields.Date(related='plan_id.week_start', store=True, readonly=True)
    week_end = fields.Date(related='plan_id.week_end', readonly=True)

    @api.depends(
        'plan_id.employee_id',
        'plan_id.week_start',
        'plan_id.week_end',
        'partner_id',
        'project_id',
        'planned_hours',
    )
    def _compute_actual_hours(self):
        AnalyticLine = self.env['account.analytic.line']
        for todo in self:
            actual = 0.0
            plan = todo.plan_id
            if plan.employee_id and plan.week_start and plan.week_end:
                domain = [
                    ('employee_id', '=', plan.employee_id.id),
                    ('date', '>=', plan.week_start),
                    ('date', '<=', plan.week_end),
                    ('project_id', '!=', False),
                ]
                if todo.project_id:
                    domain.append(('project_id', '=', todo.project_id.id))
                elif todo.partner_id:
                    domain.append(('partner_id', '=', todo.partner_id.id))
                lines = AnalyticLine.search(domain)
                actual = sum(lines.mapped('unit_amount'))
            todo.actual_hours = round(actual, 2)
            todo.variance_hours = round(actual - todo.planned_hours, 2)

    @api.onchange('partner_id')
    def _onchange_partner_id(self):
        if self.project_id and self.project_id.partner_id != self.partner_id:
            self.project_id = False
