"""
Daily Worktime Tracking and Cost Distribution System

This module provides comprehensive worktime tracking functionality that estimates
daily work time spent on issues and distributes costs proportionally based on
time allocation rather than equal distribution.
"""

import sqlite3
import json
from datetime import datetime, date, timedelta
from typing import List, Dict, Any, Optional, Tuple
from dataclasses import dataclass, asdict
from decimal import Decimal
from pathlib import Path

from .models import FinanceModels


@dataclass
class WorktimeEntry:
    """Data class representing a worktime tracking entry."""
    id: Optional[int] = None
    issue_id: int = None
    work_date: date = None
    start_time: Optional[datetime] = None
    end_time: Optional[datetime] = None
    duration_minutes: int = None
    description: Optional[str] = None
    entry_type: str = "manual"  # manual, estimated, tracked
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None


@dataclass
class DailySummary:
    """Data class for daily worktime summary."""
    work_date: date
    total_minutes: int
    issue_count: int
    entries: List[WorktimeEntry]
    cost_per_minute: Optional[Decimal] = None
    total_cost_allocated: Optional[Decimal] = None


class WorktimeTracker:
    """
    Service for tracking worktime and distributing costs based on time allocation.

    Provides functionality to log work time, estimate daily effort, and calculate
    proportional cost distribution across issues based on time spent.
    """

    def __init__(self, db_path: str = "markitect.db"):
        """
        Initialize the worktime tracker.

        Args:
            db_path: Path to the SQLite database file
        """
        self.db_path = db_path
        self.finance_models = FinanceModels(db_path)
        self._ensure_schema()

    def _ensure_schema(self):
        """Ensure the database schema is properly initialized."""
        self.finance_models.initialize_finance_schema()
        self._create_worktime_tables()

    def _create_worktime_tables(self):
        """Create worktime-specific database tables."""
        with self.finance_models.get_connection() as conn:
            cursor = conn.cursor()

            # Enable foreign key constraints
            cursor.execute('PRAGMA foreign_keys = ON')

            # Create worktime entries table
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS worktime_entries (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    issue_id INTEGER NOT NULL,
                    work_date DATE NOT NULL,
                    start_time TIMESTAMP,
                    end_time TIMESTAMP,
                    duration_minutes INTEGER NOT NULL CHECK (duration_minutes > 0),
                    description TEXT,
                    entry_type TEXT CHECK (entry_type IN ('manual', 'estimated', 'tracked')) DEFAULT 'manual',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    CONSTRAINT unique_issue_date_time UNIQUE (issue_id, work_date, start_time)
                )
            ''')

            # Create daily worktime summaries table
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS daily_worktime_summaries (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    work_date DATE NOT NULL UNIQUE,
                    total_minutes INTEGER NOT NULL DEFAULT 0,
                    issue_count INTEGER NOT NULL DEFAULT 0,
                    cost_per_minute DECIMAL(10,6),
                    total_cost_allocated DECIMAL(10,2),
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )
            ''')

            # Create cost distribution log table
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS worktime_cost_distributions (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    work_date DATE NOT NULL,
                    issue_id INTEGER NOT NULL,
                    time_minutes INTEGER NOT NULL,
                    cost_allocated DECIMAL(10,2) NOT NULL,
                    cost_per_minute DECIMAL(10,6) NOT NULL,
                    period_id INTEGER REFERENCES cost_periods(id),
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    CONSTRAINT unique_date_issue_distribution UNIQUE (work_date, issue_id)
                )
            ''')

    def log_worktime(self,
                    issue_id: int,
                    duration_minutes: int,
                    work_date: Optional[date] = None,
                    start_time: Optional[datetime] = None,
                    end_time: Optional[datetime] = None,
                    description: Optional[str] = None,
                    entry_type: str = "manual") -> int:
        """
        Log worktime for an issue.

        Args:
            issue_id: ID of the issue
            duration_minutes: Duration of work in minutes
            work_date: Date when work was performed (defaults to today)
            start_time: Optional start timestamp
            end_time: Optional end timestamp
            description: Description of work performed
            entry_type: Type of entry (manual, estimated, tracked)

        Returns:
            ID of the created worktime entry

        Raises:
            ValueError: If duration is invalid
            sqlite3.Error: If database operation fails
        """
        if duration_minutes <= 0:
            raise ValueError("Duration must be positive")

        if work_date is None:
            work_date = date.today()

        # Validate time consistency
        if start_time and end_time:
            calculated_duration = int((end_time - start_time).total_seconds() / 60)
            if abs(calculated_duration - duration_minutes) > 5:  # Allow 5-minute tolerance
                print(f"⚠️  Warning: Calculated duration ({calculated_duration}min) differs from specified ({duration_minutes}min)")

        with self.finance_models.get_connection() as conn:
            cursor = conn.cursor()

            cursor.execute('''
                INSERT INTO worktime_entries
                (issue_id, work_date, start_time, end_time, duration_minutes, description, entry_type)
                VALUES (?, ?, ?, ?, ?, ?, ?)
            ''', (issue_id, work_date.isoformat() if hasattr(work_date, 'isoformat') else work_date, start_time.isoformat() if hasattr(start_time, 'isoformat') else start_time, end_time.isoformat() if hasattr(end_time, 'isoformat') else end_time, duration_minutes, description, entry_type))

            entry_id = cursor.lastrowid

            # Update daily summary
            self._update_daily_summary(work_date, conn)

            return entry_id

    def _update_daily_summary(self, work_date: date, conn: sqlite3.Connection):
        """Update the daily worktime summary for a given date."""
        cursor = conn.cursor()

        # Calculate daily totals
        cursor.execute('''
            SELECT
                COUNT(DISTINCT issue_id) as issue_count,
                SUM(duration_minutes) as total_minutes
            FROM worktime_entries
            WHERE work_date = ?
        ''', (work_date.isoformat() if hasattr(work_date, 'isoformat') else work_date,))

        result = cursor.fetchone()
        issue_count = result[0] or 0
        total_minutes = result[1] or 0

        # Insert or update summary
        cursor.execute('''
            INSERT OR REPLACE INTO daily_worktime_summaries
            (work_date, total_minutes, issue_count)
            VALUES (?, ?, ?)
        ''', (work_date.isoformat() if hasattr(work_date, 'isoformat') else work_date, total_minutes, issue_count))

    def get_worktime_entries(self,
                           issue_id: Optional[int] = None,
                           work_date: Optional[date] = None,
                           start_date: Optional[date] = None,
                           end_date: Optional[date] = None,
                           limit: Optional[int] = None) -> List[WorktimeEntry]:
        """
        Get worktime entries with optional filtering.

        Args:
            issue_id: Filter by specific issue
            work_date: Filter by specific date
            start_date: Filter by date range start
            end_date: Filter by date range end
            limit: Maximum number of entries to return

        Returns:
            List of worktime entries
        """
        with self.finance_models.get_connection() as conn:
            cursor = conn.cursor()

            query = '''
                SELECT id, issue_id, work_date, start_time, end_time,
                       duration_minutes, description, entry_type, created_at, updated_at
                FROM worktime_entries
                WHERE 1=1
            '''
            params = []

            if issue_id:
                query += ' AND issue_id = ?'
                params.append(issue_id)

            if work_date:
                query += ' AND work_date = ?'
                params.append(work_date.isoformat() if hasattr(work_date, 'isoformat') else work_date)
            elif start_date and end_date:
                query += ' AND work_date BETWEEN ? AND ?'
                params.extend([start_date.isoformat() if hasattr(start_date, 'isoformat') else start_date, end_date.isoformat() if hasattr(end_date, 'isoformat') else end_date])
            elif start_date:
                query += ' AND work_date >= ?'
                params.append(start_date.isoformat() if hasattr(start_date, 'isoformat') else start_date)
            elif end_date:
                query += ' AND work_date <= ?'
                params.append(end_date.isoformat() if hasattr(end_date, 'isoformat') else end_date)

            query += ' ORDER BY work_date DESC, start_time DESC'

            if limit:
                query += ' LIMIT ?'
                params.append(limit)

            cursor.execute(query, params)
            rows = cursor.fetchall()

            entries = []
            for row in rows:
                entry = WorktimeEntry(
                    id=row[0],
                    issue_id=row[1],
                    work_date=datetime.strptime(row[2], '%Y-%m-%d').date() if row[2] else None,
                    start_time=datetime.fromisoformat(row[3]) if row[3] else None,
                    end_time=datetime.fromisoformat(row[4]) if row[4] else None,
                    duration_minutes=row[5],
                    description=row[6],
                    entry_type=row[7],
                    created_at=datetime.fromisoformat(row[8]) if row[8] else None,
                    updated_at=datetime.fromisoformat(row[9]) if row[9] else None
                )
                entries.append(entry)

            return entries

    def get_daily_summary(self, work_date: date) -> Optional[DailySummary]:
        """
        Get daily worktime summary for a specific date.

        Args:
            work_date: Date to get summary for

        Returns:
            Daily summary or None if no data
        """
        entries = self.get_worktime_entries(work_date=work_date)
        if not entries:
            return None

        total_minutes = sum(entry.duration_minutes for entry in entries)
        issue_count = len(set(entry.issue_id for entry in entries))

        # Get cost allocation if available
        with self.finance_models.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute('''
                SELECT cost_per_minute, total_cost_allocated
                FROM daily_worktime_summaries
                WHERE work_date = ?
            ''', (work_date.isoformat() if hasattr(work_date, 'isoformat') else work_date,))
            result = cursor.fetchone()

            cost_per_minute = Decimal(str(result[0])) if result and result[0] else None
            total_cost_allocated = Decimal(str(result[1])) if result and result[1] else None

        return DailySummary(
            work_date=work_date,
            total_minutes=total_minutes,
            issue_count=issue_count,
            entries=entries,
            cost_per_minute=cost_per_minute,
            total_cost_allocated=total_cost_allocated
        )

    def estimate_daily_worktime(self,
                              work_date: date,
                              total_hours: float = 8.0,
                              issues: Optional[List[int]] = None,
                              distribution_method: str = "equal") -> Dict[str, Any]:
        """
        Estimate worktime distribution for a day when specific times aren't tracked.

        Args:
            work_date: Date to estimate for
            total_hours: Total work hours for the day
            issues: List of issue IDs that were worked on
            distribution_method: How to distribute time (equal, activity_based, manual)

        Returns:
            Dictionary with estimation results
        """
        total_minutes = int(total_hours * 60)

        if not issues:
            # Try to identify issues from activity log
            issues = self._get_active_issues_for_date(work_date)

        if not issues:
            raise ValueError("No issues specified and none found in activity log")

        estimates = {}

        if distribution_method == "equal":
            # Equal distribution across all issues
            minutes_per_issue = total_minutes // len(issues)
            remainder = total_minutes % len(issues)

            for i, issue_id in enumerate(issues):
                minutes = minutes_per_issue + (1 if i < remainder else 0)
                estimates[issue_id] = minutes

        elif distribution_method == "activity_based":
            # Distribute based on activity frequency
            activity_weights = self._get_activity_weights_for_date(work_date, issues)
            total_weight = sum(activity_weights.values())

            if total_weight == 0:
                # Fallback to equal distribution
                minutes_per_issue = total_minutes // len(issues)
                for issue_id in issues:
                    estimates[issue_id] = minutes_per_issue
            else:
                for issue_id in issues:
                    weight = activity_weights.get(issue_id, 0)
                    minutes = int((weight / total_weight) * total_minutes)
                    estimates[issue_id] = minutes

        # Log estimated entries
        for issue_id, minutes in estimates.items():
            if minutes > 0:
                self.log_worktime(
                    issue_id=issue_id,
                    duration_minutes=minutes,
                    work_date=work_date,
                    description=f"Estimated worktime ({distribution_method} distribution)",
                    entry_type="estimated"
                )

        return {
            "work_date": work_date,
            "total_minutes": total_minutes,
            "distribution_method": distribution_method,
            "issue_estimates": estimates,
            "issues_count": len(issues)
        }

    def _get_active_issues_for_date(self, work_date: date) -> List[int]:
        """Get issues that had activity on a specific date."""
        with self.finance_models.get_connection() as conn:
            cursor = conn.cursor()
            cursor.execute('''
                SELECT DISTINCT issue_id
                FROM issue_activity_log
                WHERE activity_date = ?
            ''', (work_date,))
            return [row[0] for row in cursor.fetchall()]

    def _get_activity_weights_for_date(self, work_date: date, issues: List[int]) -> Dict[int, int]:
        """Get activity weights for issues on a specific date."""
        with self.finance_models.get_connection() as conn:
            cursor = conn.cursor()
            placeholders = ','.join(['?' for _ in issues])
            cursor.execute(f'''
                SELECT issue_id, COUNT(*) as activity_count
                FROM issue_activity_log
                WHERE activity_date = ? AND issue_id IN ({placeholders})
                GROUP BY issue_id
            ''', [work_date.isoformat() if hasattr(work_date, 'isoformat') else work_date] + issues)
            return dict(cursor.fetchall())

    def distribute_daily_costs(self,
                              work_date: date,
                              total_daily_cost: Decimal,
                              period_id: Optional[int] = None) -> Dict[str, Any]:
        """
        Distribute daily costs proportionally based on time spent on each issue.

        Args:
            work_date: Date to distribute costs for
            total_daily_cost: Total cost to distribute
            period_id: Cost period ID for tracking

        Returns:
            Dictionary with distribution results
        """
        # Get worktime entries for the date
        entries = self.get_worktime_entries(work_date=work_date)
        if not entries:
            return {
                "work_date": work_date,
                "total_cost": float(total_daily_cost),
                "distributions": {},
                "message": "No worktime entries found for this date"
            }

        # Calculate time per issue
        issue_minutes = {}
        for entry in entries:
            issue_minutes[entry.issue_id] = issue_minutes.get(entry.issue_id, 0) + entry.duration_minutes

        total_minutes = sum(issue_minutes.values())
        if total_minutes == 0:
            return {
                "work_date": work_date,
                "total_cost": float(total_daily_cost),
                "distributions": {},
                "message": "No time tracked for this date"
            }

        cost_per_minute = total_daily_cost / total_minutes

        # Calculate cost distribution
        distributions = {}
        with self.finance_models.get_connection() as conn:
            cursor = conn.cursor()

            for issue_id, minutes in issue_minutes.items():
                cost_allocated = cost_per_minute * minutes
                distributions[issue_id] = {
                    "minutes": minutes,
                    "percentage": (minutes / total_minutes) * 100,
                    "cost_allocated": float(cost_allocated),
                    "cost_per_minute": float(cost_per_minute)
                }

                # Log the distribution
                cursor.execute('''
                    INSERT OR REPLACE INTO worktime_cost_distributions
                    (work_date, issue_id, time_minutes, cost_allocated, cost_per_minute, period_id)
                    VALUES (?, ?, ?, ?, ?, ?)
                ''', (work_date.isoformat() if hasattr(work_date, 'isoformat') else work_date, issue_id, minutes, float(cost_allocated), float(cost_per_minute), period_id))

            # Update daily summary with cost information
            cursor.execute('''
                UPDATE daily_worktime_summaries
                SET cost_per_minute = ?, total_cost_allocated = ?, updated_at = CURRENT_TIMESTAMP
                WHERE work_date = ?
            ''', (float(cost_per_minute), float(total_daily_cost), work_date.isoformat() if hasattr(work_date, 'isoformat') else work_date))

        return {
            "work_date": work_date,
            "total_cost": float(total_daily_cost),
            "total_minutes": total_minutes,
            "cost_per_minute": float(cost_per_minute),
            "distributions": distributions,
            "issues_count": len(distributions)
        }

    def get_worktime_report(self,
                           start_date: Optional[date] = None,
                           end_date: Optional[date] = None,
                           issue_id: Optional[int] = None) -> Dict[str, Any]:
        """
        Generate comprehensive worktime report.

        Args:
            start_date: Report start date
            end_date: Report end date
            issue_id: Filter by specific issue

        Returns:
            Comprehensive worktime report
        """
        if not start_date:
            start_date = date.today() - timedelta(days=30)  # Last 30 days
        if not end_date:
            end_date = date.today()

        entries = self.get_worktime_entries(
            issue_id=issue_id,
            start_date=start_date,
            end_date=end_date
        )

        if not entries:
            return {
                "period": f"{start_date} to {end_date}",
                "total_entries": 0,
                "total_time": {"hours": 0, "minutes": 0},
                "summary": "No worktime entries found"
            }

        # Calculate summary statistics
        total_minutes = sum(entry.duration_minutes for entry in entries)
        issue_breakdown = {}
        daily_breakdown = {}

        for entry in entries:
            # Issue breakdown
            if entry.issue_id not in issue_breakdown:
                issue_breakdown[entry.issue_id] = {
                    "total_minutes": 0,
                    "entry_count": 0,
                    "dates": set()
                }
            issue_breakdown[entry.issue_id]["total_minutes"] += entry.duration_minutes
            issue_breakdown[entry.issue_id]["entry_count"] += 1
            issue_breakdown[entry.issue_id]["dates"].add(entry.work_date)

            # Daily breakdown
            date_str = entry.work_date.isoformat()
            if date_str not in daily_breakdown:
                daily_breakdown[date_str] = {
                    "total_minutes": 0,
                    "issues": set(),
                    "entries": 0
                }
            daily_breakdown[date_str]["total_minutes"] += entry.duration_minutes
            daily_breakdown[date_str]["issues"].add(entry.issue_id)
            daily_breakdown[date_str]["entries"] += 1

        # Convert sets to counts for JSON serialization
        for issue_id in issue_breakdown:
            issue_breakdown[issue_id]["unique_dates"] = len(issue_breakdown[issue_id]["dates"])
            del issue_breakdown[issue_id]["dates"]

        for date_str in daily_breakdown:
            daily_breakdown[date_str]["unique_issues"] = len(daily_breakdown[date_str]["issues"])
            del daily_breakdown[date_str]["issues"]

        return {
            "period": f"{start_date} to {end_date}",
            "total_entries": len(entries),
            "total_time": {
                "hours": total_minutes // 60,
                "minutes": total_minutes % 60,
                "total_minutes": total_minutes
            },
            "unique_issues": len(issue_breakdown),
            "unique_dates": len(daily_breakdown),
            "average_minutes_per_day": total_minutes / len(daily_breakdown) if daily_breakdown else 0,
            "issue_breakdown": issue_breakdown,
            "daily_breakdown": daily_breakdown
        }

    def delete_worktime_entry(self, entry_id: int) -> bool:
        """
        Delete a worktime entry and update summaries.

        Args:
            entry_id: ID of the entry to delete

        Returns:
            True if deleted successfully, False otherwise
        """
        with self.finance_models.get_connection() as conn:
            cursor = conn.cursor()

            # Get the entry details before deletion for summary update
            cursor.execute('''
                SELECT work_date FROM worktime_entries WHERE id = ?
            ''', (entry_id,))
            result = cursor.fetchone()

            if not result:
                return False

            work_date = datetime.strptime(result[0], '%Y-%m-%d').date()

            # Delete the entry
            cursor.execute('DELETE FROM worktime_entries WHERE id = ?', (entry_id,))

            if cursor.rowcount > 0:
                # Update daily summary
                self._update_daily_summary(work_date, conn)
                return True

        return False

    def update_worktime_entry(self,
                            entry_id: int,
                            duration_minutes: Optional[int] = None,
                            description: Optional[str] = None,
                            start_time: Optional[datetime] = None,
                            end_time: Optional[datetime] = None) -> bool:
        """
        Update an existing worktime entry.

        Args:
            entry_id: ID of the entry to update
            duration_minutes: New duration in minutes
            description: New description
            start_time: New start time
            end_time: New end time

        Returns:
            True if updated successfully, False otherwise
        """
        updates = {}
        params = []

        if duration_minutes is not None:
            updates["duration_minutes"] = "?"
            params.append(duration_minutes)
        if description is not None:
            updates["description"] = "?"
            params.append(description)
        if start_time is not None:
            updates["start_time"] = "?"
            params.append(start_time)
        if end_time is not None:
            updates["end_time"] = "?"
            params.append(end_time)

        if not updates:
            return False

        updates["updated_at"] = "CURRENT_TIMESTAMP"

        with self.finance_models.get_connection() as conn:
            cursor = conn.cursor()

            # Get work date for summary update
            cursor.execute('SELECT work_date FROM worktime_entries WHERE id = ?', (entry_id,))
            result = cursor.fetchone()
            if not result:
                return False

            work_date = datetime.strptime(result[0], '%Y-%m-%d').date()

            # Build update query
            set_clause = ", ".join(f"{col} = {val}" for col, val in updates.items())
            params.append(entry_id)

            cursor.execute(f'''
                UPDATE worktime_entries
                SET {set_clause}
                WHERE id = ?
            ''', params)

            if cursor.rowcount > 0:
                # Update daily summary
                self._update_daily_summary(work_date, conn)
                return True

        return False