from datetime import date, datetime, timedelta
from pathlib import Path
from uuid import uuid4

from sqlalchemy.orm import Session
from starlette.datastructures import UploadFile

from . import models
from .auth import hash_password, verify_password
from .mailer import send_email
from .repositories import (
    ProjectMemberRepository,
    ProjectRepository,
    TaskRepository,
    TaskRequestRepository,
    UserRepository,
)


class UserService:
    def __init__(self, db: Session):
        self.users = UserRepository(db)

    def create_user(self, username: str, email: str, password: str, role: str = "user"):
        if self.users.get_by_username(username):
            raise ValueError("username already exists")
        if self.users.get_by_email(email):
            raise ValueError("email already exists")
        user = models.User(
            username=username,
            email=email,
            password_hash=hash_password(password),
            role=role,
        )
        return self.users.create(user)

    def change_password(
        self,
        user: models.User,
        current_password: str,
        new_password: str,
        confirm_password: str,
    ):
        if not verify_password(current_password, user.password_hash):
            raise ValueError("current_password")
        if not new_password:
            raise ValueError("empty_password")
        if new_password != confirm_password:
            raise ValueError("password_mismatch")
        user.password_hash = hash_password(new_password)
        return self.users.update(user)

    def update_profile_photo(self, user: models.User, profile_photo: UploadFile | None, remove_photo: bool = False):
        if remove_photo and user.profile_photo_path:
            existing_path = Path("app") / user.profile_photo_path.lstrip("/")
            if existing_path.exists():
                existing_path.unlink()
            user.profile_photo_path = None

        if profile_photo and profile_photo.filename:
            extension = Path(profile_photo.filename).suffix.lower()
            if extension not in {".jpg", ".jpeg", ".png", ".gif", ".webp"}:
                raise ValueError("invalid_photo_format")

            upload_dir = Path("app/static/uploads/profiles")
            upload_dir.mkdir(parents=True, exist_ok=True)
            filename = f"user-{user.id}-{uuid4().hex}{extension}"
            destination = upload_dir / filename
            file_bytes = profile_photo.file.read()
            if not file_bytes:
                raise ValueError("empty_photo")
            destination.write_bytes(file_bytes)

            if user.profile_photo_path:
                existing_path = Path("app") / user.profile_photo_path.lstrip("/")
                if existing_path.exists():
                    existing_path.unlink()

            user.profile_photo_path = f"/static/uploads/profiles/{filename}"

        return self.users.update(user)

    def admin_update_user(
        self,
        user: models.User,
        email: str,
        password: str | None = None,
    ):
        email = email.strip().lower()
        if not email:
            raise ValueError("empty_email")

        existing = self.users.get_by_email(email)
        if existing and existing.id != user.id:
            raise ValueError("email_exists")

        user.email = email

        if password:
            user.password_hash = hash_password(password)

        return self.users.update(user)


class ProjectService:
    def __init__(self, db: Session):
        self.projects = ProjectRepository(db)
        self.members = ProjectMemberRepository(db)

    def create_project(self, name: str, description: str | None, is_group: bool, owner_id: int):
        project = models.Project(name=name, description=description, is_group=is_group)
        project = self.projects.create(project)
        self.members.add_member(project.id, owner_id, role="owner")
        return project

    def add_member(self, project_id: int, user_id: int):
        if self.members.is_member(project_id, user_id):
            raise ValueError("user already in project")
        return self.members.add_member(project_id, user_id)


class TaskService:
    def __init__(self, db: Session):
        self.tasks = TaskRepository(db)
        self.members = ProjectMemberRepository(db)
        self.users = UserRepository(db)
        self.projects = ProjectRepository(db)

    def create_task(
        self,
        project_id: int,
        title: str,
        description: str | None,
        priority: str,
        duration_days: int,
        created_by_id: int,
        assigned_user_id: int | None = None,
    ):
        task = models.Task(
            project_id=project_id,
            title=title,
            description=description,
            priority=priority,
            duration_days=duration_days,
            created_by_id=created_by_id,
            assigned_user_id=assigned_user_id,
        )
        task = self.tasks.create(task)
        if assigned_user_id and not self.members.is_member(project_id, assigned_user_id):
            self.members.add_member(project_id, assigned_user_id)
        if task.assigned_user:
            project = self.projects.get(project_id)
            _send_task_assignment_email(task.assigned_user, task, project)
        return task

    def update_status(self, task: models.Task, status: str, actor: models.User):
        if actor.role != "admin" and task.assigned_user_id != actor.id:
            raise PermissionError("not allowed to move this task")
        if status == "in_progress" and task.status != "in_progress":
            task.started_at = datetime.utcnow()
        if status == "not_started":
            task.started_at = None
            task.overdue_alert_sent_at = None
        task.status = status
        return self.tasks.update(task)

    def update_support_url(self, task: models.Task, support_url: str | None, actor: models.User):
        if actor.role != "admin" and task.assigned_user_id != actor.id:
            raise PermissionError("not allowed to update support url")
        if task.status != "done":
            raise ValueError("task not finished")
        task.support_url = support_url or None
        return self.tasks.update(task)

    def assign_task(self, task: models.Task, user_id: int):
        task.assigned_user_id = user_id
        task = self.tasks.update(task)
        if not self.members.is_member(task.project_id, user_id):
            self.members.add_member(task.project_id, user_id)
        assigned_user = self.users.get_by_id(user_id)
        project = self.projects.get(task.project_id)
        if assigned_user:
            _send_task_assignment_email(assigned_user, task, project)
        return task

    def delete_task(self, task: models.Task, actor: models.User):
        if task.status != "not_started":
            raise ValueError("task already started")
        if actor.role != "admin" and task.created_by_id != actor.id:
            raise PermissionError("not allowed to delete this task")
        self.tasks.delete(task)


class TaskRequestService:
    def __init__(self, db: Session):
        self.requests = TaskRequestRepository(db)
        self.tasks = TaskRepository(db)
        self.users = UserRepository(db)
        self.members = ProjectMemberRepository(db)

    def create_request(self, task_id: int, requester_id: int, target_user_id: int):
        task = self.tasks.get(task_id)
        if not task:
            raise ValueError("task not found")
        if not self.users.get_by_id(target_user_id):
            raise ValueError("target user not found")
        if requester_id == target_user_id:
            raise ValueError("cannot request yourself")
        request = models.TaskRequest(
            task_id=task_id,
            requester_id=requester_id,
            target_user_id=target_user_id,
        )
        request = self.requests.create(request)
        target_user = self.users.get_by_id(target_user_id)
        requester = self.users.get_by_id(requester_id)
        if target_user and requester:
            _send_support_request_email(target_user, requester, task)
        return request

    def respond(self, request: models.TaskRequest, accept: bool, responder_id: int):
        if request.target_user_id != responder_id:
            raise PermissionError("not allowed to respond")
        request.status = "accepted" if accept else "rejected"
        request.responded_at = datetime.utcnow()
        request = self.requests.update(request)
        if accept:
            task = self.tasks.get(request.task_id)
            task.assigned_user_id = responder_id
            self.tasks.update(task)
            if not self.members.is_member(task.project_id, responder_id):
                self.members.add_member(task.project_id, responder_id)
        requester = self.users.get_by_id(request.requester_id)
        responder = self.users.get_by_id(responder_id)
        task = self.tasks.get(request.task_id)
        if requester and responder and task:
            _send_request_response_email(requester, responder, task, accept)
        return request


def send_overdue_alerts(db: Session, tasks: list[models.Task], reference: datetime | None = None):
    reference = reference or datetime.utcnow()
    user_repo = UserRepository(db)
    admins = [admin for admin in user_repo.list_admins() if admin.email]
    task_repo = TaskRepository(db)
    project_repo = ProjectRepository(db)

    for task in tasks:
        if task.status != "in_progress" or not task.assigned_user or not task.assigned_user.email:
            continue

        metrics = task_progress_metrics(task, reference)
        if metrics["overdue_days"] <= 0:
            if task.overdue_alert_sent_at is not None:
                task.overdue_alert_sent_at = None
                task_repo.update(task)
            continue

        if task.overdue_alert_sent_at and (reference - task.overdue_alert_sent_at) < timedelta(hours=24):
            continue

        project = project_repo.get(task.project_id)
        subject = f"[Kanban] Actividad retrasada: {task.title}"
        body = (
            f"Hola {task.assigned_user.username},\n\n"
            f"La actividad '{task.title}' del proyecto '{project.name if project else task.project_id}' "
            f"tiene un retraso de {metrics['overdue_days']} dia(s).\n"
            f"Tiempo en progreso: {metrics['elapsed_days']} dia(s).\n"
            f"Duracion estimada: {task.duration_days} dia(s).\n\n"
            "Ingresa al tablero para actualizar el avance."
        )
        _safe_send_email(task.assigned_user.email, subject, body)

        for admin in admins:
            body_admin = (
                f"Hola {admin.username},\n\n"
                f"La actividad '{task.title}' del proyecto '{project.name if project else task.project_id}' "
                f"asignada a {task.assigned_user.username} presenta un retraso de {metrics['overdue_days']} dia(s)."
            )
            _safe_send_email(admin.email, subject, body_admin)

        task.overdue_alert_sent_at = reference
        task_repo.update(task)


def task_progress_metrics(task: models.Task, reference: datetime | None = None) -> dict[str, int]:
    reference = reference or datetime.utcnow()
    elapsed_days = 0
    if task.started_at:
        elapsed_days = business_days_between(task.started_at.date(), reference.date())
    remaining_days = task.duration_days - elapsed_days
    return {
        "elapsed_days": elapsed_days,
        "remaining_days": remaining_days,
        "overdue_days": abs(remaining_days) if remaining_days < 0 else 0,
    }


def business_days_between(start_date: date, end_date: date) -> int:
    if end_date < start_date:
        return 0
    days = 0
    current = start_date
    while current <= end_date:
        if current.weekday() < 5:
            days += 1
        current += timedelta(days=1)
    return days


def _send_task_assignment_email(user: models.User, task: models.Task, project: models.Project | None):
    body = (
        f"Hola {user.username},\n\n"
        f"Se te asigno la actividad '{task.title}'"
        f"{f' del proyecto {project.name}' if project else ''}.\n"
        f"Prioridad: {task.priority}.\n"
        f"Duracion estimada: {task.duration_days} dia(s).\n\n"
        "Ingresa a la plataforma para revisar el detalle."
    )
    _safe_send_email(user.email, f"[Kanban] Nueva actividad asignada: {task.title}", body)


def _send_support_request_email(target_user: models.User, requester: models.User, task: models.Task):
    body = (
        f"Hola {target_user.username},\n\n"
        f"{requester.username} solicito tu apoyo en la actividad '{task.title}'.\n\n"
        "Ingresa a la plataforma para aceptar o rechazar la solicitud."
    )
    _safe_send_email(target_user.email, f"[Kanban] Solicitud de apoyo: {task.title}", body)


def _send_request_response_email(requester: models.User, responder: models.User, task: models.Task, accept: bool):
    decision = "aceptada" if accept else "rechazada"
    body = (
        f"Hola {requester.username},\n\n"
        f"{responder.username} ha {decision} tu solicitud de apoyo para la actividad '{task.title}'."
    )
    _safe_send_email(requester.email, f"[Kanban] Solicitud {decision}: {task.title}", body)


def _safe_send_email(recipient: str | None, subject: str, body: str):
    if not recipient:
        return
    try:
        send_email(recipient, subject, body)
    except Exception:
        return
