from datetime import datetime
from decimal import Decimal

from app import db


class Project(db.Model):
    """Project model for client projects with billing information"""

    __tablename__ = "projects"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=False, index=True)
    client_id = db.Column(db.Integer, db.ForeignKey("clients.id"), nullable=False, index=True)
    quote_id = db.Column(db.Integer, db.ForeignKey("quotes.id"), nullable=True, index=True)
    description = db.Column(db.Text, nullable=True)
    billable = db.Column(db.Boolean, default=True, nullable=False)
    hourly_rate = db.Column(db.Numeric(9, 2), nullable=True)
    billing_ref = db.Column(db.String(100), nullable=True)
    # Short project code for compact display (e.g., on Kanban cards)
    code = db.Column(db.String(20), nullable=True, unique=True, index=True)
    status = db.Column(db.String(20), default="active", nullable=False)  # 'active', 'inactive', or 'archived'
    # Estimates & budgets
    estimated_hours = db.Column(db.Float, nullable=True)
    budget_amount = db.Column(db.Numeric(10, 2), nullable=True)
    budget_threshold_percent = db.Column(db.Integer, nullable=False, default=80)  # alert when exceeded
    custom_fields = db.Column(db.JSON, nullable=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
    # Archiving metadata
    archived_at = db.Column(db.DateTime, nullable=True, index=True)
    archived_by = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
    archived_reason = db.Column(db.Text, nullable=True)
    # Gantt chart bar color (hex e.g. #3b82f6)
    color = db.Column(db.String(7), nullable=True)

    # Relationships
    time_entries = db.relationship("TimeEntry", backref="project", lazy="dynamic", cascade="all, delete-orphan")
    tasks = db.relationship("Task", backref="project", lazy="dynamic", cascade="all, delete-orphan")
    costs = db.relationship("ProjectCost", backref="project", lazy="dynamic", cascade="all, delete-orphan")
    extra_goods = db.relationship("ExtraGood", backref="project", lazy="dynamic", cascade="all, delete-orphan")
    # comments relationship is defined via backref in Comment model

    def __init__(
        self,
        name,
        client_id=None,
        description=None,
        billable=True,
        hourly_rate=None,
        billing_ref=None,
        client=None,
        budget_amount=None,
        budget_threshold_percent=80,
        code=None,
        created_by=None,
        status="active",
    ):
        """Create a Project.

        Backward-compatible initializer that accepts either client_id or client name.
        If client name is provided and client_id is not, the corresponding Client
        record will be found or created on the fly and client_id will be set.

        Note: created_by parameter is accepted for test compatibility but not used,
        as the Project model doesn't track creator information.
        """
        from .client import Client  # local import to avoid circular dependencies

        self.name = name.strip()
        self.description = description.strip() if description else None
        self.billable = billable
        self.hourly_rate = Decimal(str(hourly_rate)) if hourly_rate else None
        self.billing_ref = billing_ref.strip() if billing_ref else None
        self.code = code.strip().upper() if code and code.strip() else None
        self.budget_amount = Decimal(str(budget_amount)) if budget_amount else None
        self.budget_threshold_percent = budget_threshold_percent if budget_threshold_percent else 80
        self.status = status

        resolved_client_id = client_id
        if resolved_client_id is None and client:
            # Find or create client by name
            client_name = client.strip()
            existing = Client.query.filter_by(name=client_name).first()
            if existing:
                resolved_client_id = existing.id
            else:
                new_client = Client(name=client_name)
                db.session.add(new_client)
                # Flush to obtain id without committing the whole transaction
                try:
                    db.session.flush()
                    resolved_client_id = new_client.id
                except Exception:
                    # If flush fails, fallback to committing
                    db.session.commit()
                    resolved_client_id = new_client.id

        self.client_id = resolved_client_id

    def __repr__(self):
        return f'<Project {self.name} ({self.client_obj.name if self.client_obj else "Unknown Client"})>'

    @property
    def client(self):
        """Get client name for backward compatibility"""
        return self.client_obj.name if self.client_obj else "Unknown Client"

    @property
    def is_active(self):
        """Check if project is active"""
        return self.status == "active"

    @property
    def is_archived(self):
        """Check if project is archived"""
        return self.status == "archived"

    @property
    def archived_by_user(self):
        """Get the user who archived this project"""
        if self.archived_by:
            from .user import User

            return User.query.get(self.archived_by)
        return None

    @property
    def code_display(self):
        """Return configured short code or a fallback derived from project name.

        Fallback: first 4 non-space characters of the project name, uppercased.
        """
        if self.code:
            return self.code
        try:
            base = (self.name or "").replace(" ", "")
            return (base.upper()[:4]) if base else ""
        except Exception:
            return ""

    @property
    def total_hours(self):
        """Calculate total hours spent on this project"""
        from .time_entry import TimeEntry

        total_seconds = (
            db.session.query(db.func.sum(TimeEntry.duration_seconds))
            .filter(TimeEntry.project_id == self.id, TimeEntry.end_time.isnot(None))
            .scalar()
            or 0
        )
        return round(total_seconds / 3600, 2)

    @property
    def total_billable_hours(self):
        """Calculate total billable hours spent on this project"""
        from .time_entry import TimeEntry

        total_seconds = (
            db.session.query(db.func.sum(TimeEntry.duration_seconds))
            .filter(TimeEntry.project_id == self.id, TimeEntry.end_time.isnot(None), TimeEntry.billable == True)
            .scalar()
            or 0
        )
        return round(total_seconds / 3600, 2)

    @property
    def estimated_cost(self):
        """Calculate estimated cost based on billable hours and hourly rate"""
        if not self.billable or not self.hourly_rate:
            return 0.0
        return float(self.total_billable_hours) * float(self.hourly_rate)

    @property
    def total_costs(self):
        """Calculate total project costs (expenses)"""
        from .project_cost import ProjectCost

        total = (
            db.session.query(db.func.sum(ProjectCost.amount)).filter(ProjectCost.project_id == self.id).scalar() or 0
        )
        return float(total)

    @property
    def total_billable_costs(self):
        """Calculate total billable project costs"""
        from .project_cost import ProjectCost

        total = (
            db.session.query(db.func.sum(ProjectCost.amount))
            .filter(ProjectCost.project_id == self.id, ProjectCost.billable == True)
            .scalar()
            or 0
        )
        return float(total)

    @property
    def total_project_value(self):
        """Calculate total project value (billable hours + billable costs)"""
        return self.estimated_cost + self.total_billable_costs

    @property
    def actual_hours(self):
        """Alias for total hours for clarity in estimates vs actuals."""
        return self.total_hours

    @property
    def budget_consumed_amount(self):
        """Compute consumed budget using effective rate logic when available.

        Falls back to project.hourly_rate if no overrides are present.
        """
        try:
            from .rate_override import RateOverride

            hours = self.total_billable_hours
            # Use project-level override if present, else project rate
            rate = RateOverride.resolve_rate(self, user_id=None)
            return float(hours * float(rate))
        except Exception:
            if self.hourly_rate:
                return float(self.total_billable_hours * float(self.hourly_rate))
            return 0.0

    @property
    def budget_threshold_exceeded(self):
        if not self.budget_amount:
            return False
        try:
            threshold = (self.budget_threshold_percent or 0) / 100.0
            return self.budget_consumed_amount >= float(self.budget_amount) * threshold
        except Exception:
            return False

    def get_entries_by_user(self, user_id=None, start_date=None, end_date=None):
        """Get time entries for this project, optionally filtered by user and date range"""
        from .time_entry import TimeEntry

        query = self.time_entries.filter(TimeEntry.end_time.isnot(None))

        if user_id:
            query = query.filter(TimeEntry.user_id == user_id)

        if start_date:
            query = query.filter(TimeEntry.start_time >= start_date)

        if end_date:
            query = query.filter(TimeEntry.start_time <= end_date)

        return query.order_by(TimeEntry.start_time.desc()).all()

    def get_user_totals(self, start_date=None, end_date=None):
        """Get total hours per user for this project"""
        from .time_entry import TimeEntry
        from .user import User

        query = (
            db.session.query(
                User.id, User.username, User.full_name, db.func.sum(TimeEntry.duration_seconds).label("total_seconds")
            )
            .join(TimeEntry)
            .filter(TimeEntry.project_id == self.id, TimeEntry.end_time.isnot(None))
        )

        if start_date:
            query = query.filter(TimeEntry.start_time >= start_date)

        if end_date:
            query = query.filter(TimeEntry.start_time <= end_date)

        results = query.group_by(User.id, User.username, User.full_name).all()

        return [
            {
                "username": (full_name.strip() if full_name and full_name.strip() else username),
                "total_hours": round(total_seconds / 3600, 2),
            }
            for _id, username, full_name, total_seconds in results
        ]

    def archive(self, user_id=None, reason=None):
        """Archive the project with metadata

        Args:
            user_id: ID of the user archiving the project
            reason: Optional reason for archiving
        """
        self.status = "archived"
        self.archived_at = datetime.utcnow()
        self.archived_by = user_id
        self.archived_reason = reason
        self.updated_at = datetime.utcnow()
        db.session.commit()

    def unarchive(self):
        """Unarchive the project and clear archiving metadata"""
        self.status = "active"
        self.archived_at = None
        self.archived_by = None
        self.archived_reason = None
        self.updated_at = datetime.utcnow()
        db.session.commit()

    def deactivate(self):
        """Mark project as inactive"""
        self.status = "inactive"
        self.updated_at = datetime.utcnow()
        db.session.commit()

    def activate(self):
        """Activate the project"""
        self.status = "active"
        self.updated_at = datetime.utcnow()
        db.session.commit()

    def is_favorited_by(self, user):
        """Check if this project is favorited by a specific user"""
        from .user import User

        if isinstance(user, int):
            user_id = user
            return self.favorited_by.filter_by(id=user_id).count() > 0
        elif isinstance(user, User):
            return self.favorited_by.filter_by(id=user.id).count() > 0
        return False

    def get_custom_field(self, key, default=None):
        """Get a custom field value"""
        if not self.custom_fields:
            return default
        return self.custom_fields.get(key, default)

    def set_custom_field(self, key, value):
        """Set a custom field value"""
        if self.custom_fields is None:
            self.custom_fields = {}
        self.custom_fields[key] = value
        self.updated_at = datetime.utcnow()

    def remove_custom_field(self, key):
        """Remove a custom field"""
        if self.custom_fields and key in self.custom_fields:
            del self.custom_fields[key]
            self.updated_at = datetime.utcnow()

    def get_rendered_links(self):
        """Get all rendered links from active link templates that match this project's custom fields"""
        from .link_template import LinkTemplate

        if not self.custom_fields:
            return []

        links = []
        templates = LinkTemplate.get_active_templates()

        for template in templates:
            if template.field_key in self.custom_fields:
                field_value = self.custom_fields[template.field_key]
                if field_value:
                    rendered_url = template.render_url(field_value)
                    if rendered_url:
                        links.append(
                            {
                                "name": template.name,
                                "url": rendered_url,
                                "icon": template.icon,
                                "description": template.description,
                            }
                        )

        return links

    def to_dict(self, user=None):
        """Convert project to dictionary for API responses"""
        data = {
            "id": self.id,
            "name": self.name,
            "code": self.code,
            "code_display": self.code_display,
            "client": self.client,
            "description": self.description,
            "billable": self.billable,
            "hourly_rate": float(self.hourly_rate) if self.hourly_rate else None,
            "billing_ref": self.billing_ref,
            "status": self.status,
            "estimated_hours": self.estimated_hours,
            "budget_amount": float(self.budget_amount) if self.budget_amount else None,
            "budget_threshold_percent": self.budget_threshold_percent,
            "created_at": self.created_at.isoformat() if self.created_at else None,
            "updated_at": self.updated_at.isoformat() if self.updated_at else None,
            "total_hours": self.total_hours,
            "total_billable_hours": self.total_billable_hours,
            "estimated_cost": float(self.estimated_cost) if self.estimated_cost else None,
            "budget_consumed_amount": self.budget_consumed_amount,
            "budget_threshold_exceeded": self.budget_threshold_exceeded,
            "total_costs": self.total_costs,
            "total_billable_costs": self.total_billable_costs,
            "total_project_value": self.total_project_value,
            "custom_fields": self.custom_fields or {},
            # Archiving metadata
            "is_archived": self.is_archived,
            "archived_at": self.archived_at.isoformat() if self.archived_at else None,
            "archived_by": self.archived_by,
            "archived_reason": self.archived_reason,
            "color": self.color,
        }
        # Include favorite status if user is provided
        if user:
            data["is_favorite"] = self.is_favorited_by(user)
        return data
