"""
Test suite for bulk task operations.
Tests bulk delete, bulk status change, bulk assignment, bulk due date, bulk priority, and bulk move to project.
"""

import json
import pytest
from flask import url_for
from app.models import Task, Project, User, TaskActivity
from app import db


# ============================================================================
# Fixtures
# ============================================================================


@pytest.fixture
def tasks_for_bulk(app, user, admin_user, project):
    """Create multiple tasks for bulk operations testing."""
    with app.app_context():
        tasks = []
        for i in range(5):
            task = Task(
                project_id=project.id,
                name=f"Bulk Test Task {i+1}",
                description=f"Task {i+1} for bulk operations",
                priority="medium",
                status="todo",
                created_by=user.id,
            )
            db.session.add(task)
            tasks.append(task)

        db.session.commit()

        # Refresh to get IDs
        for task in tasks:
            db.session.refresh(task)

        return tasks


@pytest.fixture
def second_project(app):
    """Create a second project for move operations testing."""
    with app.app_context():
        from app.models import Client as ClientModel

        # Create or get a client for the second project
        project_client = ClientModel.query.first()
        if not project_client:
            project_client = ClientModel(name="Test Client 2", email="client2@example.com", created_by=1)
            db.session.add(project_client)
            db.session.commit()
            db.session.refresh(project_client)

        project = Project(
            name="Second Project", client_id=project_client.id, billable=True, status="active", created_by=1
        )
        db.session.add(project)
        db.session.commit()
        db.session.refresh(project)

        return project


# ============================================================================
# Bulk Delete Tests
# ============================================================================


@pytest.mark.unit
@pytest.mark.routes
def test_bulk_delete_no_tasks_selected(authenticated_client):
    """Test bulk delete with no tasks selected."""
    response = authenticated_client.post("/tasks/bulk-delete", data={"task_ids[]": []}, follow_redirects=True)

    assert response.status_code == 200
    assert b"No tasks selected" in response.data or b"No tasks" in response.data


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_delete_multiple_tasks(authenticated_client, app, tasks_for_bulk):
    """Test bulk deleting multiple tasks."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:3]]

        response = authenticated_client.post("/tasks/bulk-delete", data={"task_ids[]": task_ids}, follow_redirects=True)

        assert response.status_code == 200
        assert b"Successfully deleted" in response.data or b"deleted" in response.data

        # Verify tasks are deleted
        for task_id in task_ids:
            task = Task.query.get(int(task_id))
            assert task is None


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_delete_with_time_entries_skips_task(authenticated_client, app, user, project):
    """Test that bulk delete skips tasks with time entries."""
    with app.app_context():
        # Create task with time entry
        task = Task(project_id=project.id, name="Task with Time Entry", created_by=user.id)
        db.session.add(task)
        db.session.commit()
        db.session.refresh(task)

        from factories import TimeEntryFactory
        from datetime import datetime

        entry = TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            task_id=task.id,
            start_time=datetime.utcnow(),
            end_time=datetime.utcnow(),
            duration_seconds=3600,
        )
        db.session.commit()

        response = authenticated_client.post(
            "/tasks/bulk-delete", data={"task_ids[]": [str(task.id)]}, follow_redirects=True
        )

        assert response.status_code == 200
        assert b"Skipped" in response.data or b"time entries" in response.data

        # Verify task still exists
        task = Task.query.get(task.id)
        assert task is not None


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_delete_permission_check(client, app, admin_user, user, project):
    """Test that non-admin users can only delete their own tasks."""
    with app.app_context():
        # Create task owned by admin
        admin_task = Task(project_id=project.id, name="Admin Task", created_by=admin_user.id)
        db.session.add(admin_task)
        db.session.commit()
        db.session.refresh(admin_task)

        # Try to delete as regular user
        with client.session_transaction() as sess:
            sess["_user_id"] = str(user.id)

        response = client.post("/tasks/bulk-delete", data={"task_ids[]": [str(admin_task.id)]}, follow_redirects=True)

        assert response.status_code == 200

        # Verify task still exists (skipped due to no permission)
        task = Task.query.get(admin_task.id)
        assert task is not None


# ============================================================================
# Bulk Status Change Tests
# ============================================================================


@pytest.mark.unit
@pytest.mark.routes
def test_bulk_status_no_tasks_selected(authenticated_client):
    """Test bulk status change with no tasks selected."""
    response = authenticated_client.post(
        "/tasks/bulk-status", data={"task_ids[]": [], "status": "in_progress"}, follow_redirects=True
    )

    assert response.status_code == 200
    assert b"No tasks selected" in response.data or b"No tasks" in response.data


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_status_change_multiple_tasks(authenticated_client, app, tasks_for_bulk):
    """Test changing status for multiple tasks."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:3]]

        response = authenticated_client.post(
            "/tasks/bulk-status", data={"task_ids[]": task_ids, "status": "in_progress"}, follow_redirects=True
        )

        assert response.status_code == 200
        assert b"Successfully updated" in response.data or b"updated" in response.data

        # Verify status is changed
        for task_id in task_ids:
            task = Task.query.get(int(task_id))
            assert task is not None
            assert task.status == "in_progress"


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_status_invalid_status(authenticated_client, app, tasks_for_bulk):
    """Test bulk status change with invalid status."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:2]]

        response = authenticated_client.post(
            "/tasks/bulk-status", data={"task_ids[]": task_ids, "status": "invalid_status"}, follow_redirects=True
        )

        assert response.status_code == 200
        assert b"Invalid status" in response.data or b"error" in response.data.lower()


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_status_reopen_from_done(authenticated_client, app, tasks_for_bulk):
    """Test bulk status change to reopen completed tasks."""
    with app.app_context():
        # Mark tasks as done first
        for task in tasks_for_bulk[:2]:
            task.status = "done"
            from datetime import datetime

            task.completed_at = datetime.utcnow()
        db.session.commit()

        task_ids = [str(task.id) for task in tasks_for_bulk[:2]]

        response = authenticated_client.post(
            "/tasks/bulk-status", data={"task_ids[]": task_ids, "status": "in_progress"}, follow_redirects=True
        )

        assert response.status_code == 200

        # Verify completed_at is cleared
        for task_id in task_ids:
            task = Task.query.get(int(task_id))
            assert task.status == "in_progress"
            assert task.completed_at is None


# ============================================================================
# Bulk Assignment Tests
# ============================================================================


@pytest.mark.unit
@pytest.mark.routes
def test_bulk_assign_no_tasks_selected(authenticated_client, user):
    """Test bulk assignment with no tasks selected."""
    response = authenticated_client.post(
        "/tasks/bulk-assign", data={"task_ids[]": [], "assigned_to": user.id}, follow_redirects=True
    )

    assert response.status_code == 200
    assert b"No tasks selected" in response.data or b"No tasks" in response.data


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_assign_multiple_tasks(authenticated_client, app, tasks_for_bulk, admin_user):
    """Test assigning multiple tasks to a user."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:3]]

        response = authenticated_client.post(
            "/tasks/bulk-assign", data={"task_ids[]": task_ids, "assigned_to": admin_user.id}, follow_redirects=True
        )

        assert response.status_code == 200
        assert b"Successfully assigned" in response.data or b"assigned" in response.data

        # Verify assignment
        for task_id in task_ids:
            task = Task.query.get(int(task_id))
            assert task is not None
            assert task.assigned_to == admin_user.id


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_assign_no_user_selected(authenticated_client, app, tasks_for_bulk):
    """Test bulk assignment without selecting a user."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:2]]

        response = authenticated_client.post("/tasks/bulk-assign", data={"task_ids[]": task_ids}, follow_redirects=True)

        assert response.status_code == 200
        assert b"No user selected" in response.data or b"error" in response.data.lower()


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_assign_invalid_user(authenticated_client, app, tasks_for_bulk):
    """Test bulk assignment with invalid user ID."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:2]]

        response = authenticated_client.post(
            "/tasks/bulk-assign",
            data={"task_ids[]": task_ids, "assigned_to": 99999},  # Non-existent user ID
            follow_redirects=True,
        )

        assert response.status_code == 200
        assert b"Invalid user" in response.data or b"error" in response.data.lower()


# ============================================================================
# Bulk Move to Project Tests
# ============================================================================


@pytest.mark.unit
@pytest.mark.routes
def test_bulk_move_project_no_tasks_selected(authenticated_client, project):
    """Test bulk move to project with no tasks selected."""
    response = authenticated_client.post(
        "/tasks/bulk-move-project", data={"task_ids[]": [], "project_id": project.id}, follow_redirects=True
    )

    assert response.status_code == 200
    assert b"No tasks selected" in response.data or b"No tasks" in response.data


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_move_project_multiple_tasks(authenticated_client, app, tasks_for_bulk, second_project):
    """Test moving multiple tasks to a different project."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:3]]
        original_project_id = tasks_for_bulk[0].project_id

        response = authenticated_client.post(
            "/tasks/bulk-move-project",
            data={"task_ids[]": task_ids, "project_id": second_project.id},
            follow_redirects=True,
        )

        assert response.status_code == 200
        assert b"Successfully moved" in response.data or b"moved" in response.data

        # Verify project change
        for task_id in task_ids:
            task = Task.query.get(int(task_id))
            assert task is not None
            assert task.project_id == second_project.id
            assert task.project_id != original_project_id


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_move_project_updates_time_entries(authenticated_client, app, user, project, second_project):
    """Test that bulk move to project updates related time entries."""
    with app.app_context():
        # Create task with time entry
        task = Task(project_id=project.id, name="Task with Time Entry", created_by=user.id)
        db.session.add(task)
        db.session.commit()
        db.session.refresh(task)

        from factories import TimeEntryFactory
        from datetime import datetime

        entry = TimeEntryFactory(
            user_id=user.id,
            project_id=project.id,
            task_id=task.id,
            start_time=datetime.utcnow(),
            end_time=datetime.utcnow(),
            duration_seconds=3600,
        )
        db.session.commit()
        db.session.refresh(entry)

        response = authenticated_client.post(
            "/tasks/bulk-move-project",
            data={"task_ids[]": [str(task.id)], "project_id": second_project.id},
            follow_redirects=True,
        )

        assert response.status_code == 200

        # Verify time entry project is updated
        from app.models import TimeEntry

        entry = TimeEntry.query.get(entry.id)
        assert entry.project_id == second_project.id


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_move_project_no_project_selected(authenticated_client, app, tasks_for_bulk):
    """Test bulk move to project without selecting a project."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:2]]

        response = authenticated_client.post(
            "/tasks/bulk-move-project", data={"task_ids[]": task_ids}, follow_redirects=True
        )

        assert response.status_code == 200
        assert b"No project selected" in response.data or b"error" in response.data.lower()


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_move_project_invalid_project(authenticated_client, app, tasks_for_bulk):
    """Test bulk move to project with invalid project ID."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:2]]

        response = authenticated_client.post(
            "/tasks/bulk-move-project",
            data={"task_ids[]": task_ids, "project_id": 99999},  # Non-existent project ID
            follow_redirects=True,
        )

        assert response.status_code == 200
        assert b"Invalid project" in response.data or b"error" in response.data.lower()


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_move_project_logs_activity(authenticated_client, app, tasks_for_bulk, second_project):
    """Test that bulk move to project logs task activity."""
    with app.app_context():
        task_ids = [str(task.id) for task in tasks_for_bulk[:2]]

        response = authenticated_client.post(
            "/tasks/bulk-move-project",
            data={"task_ids[]": task_ids, "project_id": second_project.id},
            follow_redirects=True,
        )

        assert response.status_code == 200

        # Verify activity is logged
        for task_id in task_ids:
            task = Task.query.get(int(task_id))
            activities = task.activities.filter_by(event="project_change").all()
            assert len(activities) > 0


# ============================================================================
# Smoke Tests
# ============================================================================


@pytest.mark.smoke
@pytest.mark.routes
def test_bulk_operations_routes_exist(authenticated_client):
    """Smoke test to verify bulk operations routes exist."""
    # Test bulk delete route
    response = authenticated_client.post("/tasks/bulk-delete", data={"task_ids[]": []}, follow_redirects=True)
    assert response.status_code == 200

    # Test bulk status route
    response = authenticated_client.post(
        "/tasks/bulk-status", data={"task_ids[]": [], "status": "todo"}, follow_redirects=True
    )
    assert response.status_code == 200

    # Test bulk assign route
    response = authenticated_client.post("/tasks/bulk-assign", data={"task_ids[]": []}, follow_redirects=True)
    assert response.status_code == 200

    # Test bulk move project route
    response = authenticated_client.post("/tasks/bulk-move-project", data={"task_ids[]": []}, follow_redirects=True)
    assert response.status_code == 200

    # Test bulk due date route (form: no tasks -> redirect with flash)
    response = authenticated_client.post(
        "/tasks/bulk-update-due-date",
        data={"task_ids[]": [], "due_date": "2025-12-31"},
        follow_redirects=True,
    )
    assert response.status_code == 200

    # Test bulk priority route (form: no tasks -> redirect with flash)
    response = authenticated_client.post(
        "/tasks/bulk-priority",
        data={"task_ids[]": [], "priority": "high"},
        follow_redirects=True,
    )
    assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_update_due_date_route(authenticated_client, app, tasks_for_bulk):
    """Smoke test: bulk due date update route is callable (form data)."""
    with app.app_context():
        task_ids = [str(t.id) for t in tasks_for_bulk[:2]]
        response = authenticated_client.post(
            "/tasks/bulk-update-due-date",
            data={"task_ids[]": task_ids, "due_date": "2025-12-31"},
            follow_redirects=True,
        )
        assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_bulk_update_priority_route(authenticated_client, app, tasks_for_bulk):
    """Smoke test: bulk priority update route is callable (form data)."""
    with app.app_context():
        task_ids = [str(t.id) for t in tasks_for_bulk[:2]]
        response = authenticated_client.post(
            "/tasks/bulk-priority",
            data={"task_ids[]": task_ids, "priority": "high"},
            follow_redirects=True,
        )
        assert response.status_code == 200


@pytest.mark.smoke
@pytest.mark.routes
def test_task_list_has_checkboxes(authenticated_client):
    """Smoke test to verify task list page has checkboxes for bulk operations."""
    response = authenticated_client.get("/tasks")
    assert response.status_code == 200
    assert b"task-checkbox" in response.data or b"checkbox" in response.data
    assert b"selectAll" in response.data or b"select" in response.data.lower()


# ============================================================================
# CSV Export Tests
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_export_tasks_csv(authenticated_client, app, tasks_for_bulk):
    """Test exporting tasks to CSV."""
    with app.app_context():
        response = authenticated_client.get("/tasks/export")

        assert response.status_code == 200
        assert response.mimetype == "text/csv"
        assert "attachment" in response.headers.get("Content-Disposition", "")

        # Check CSV content
        csv_data = response.data.decode("utf-8")
        assert "ID" in csv_data
        assert "Name" in csv_data
        assert "Project" in csv_data
        assert "Status" in csv_data

        # Check that task data is in CSV
        assert tasks_for_bulk[0].name in csv_data


@pytest.mark.integration
@pytest.mark.routes
def test_export_tasks_with_filters(authenticated_client, app, tasks_for_bulk):
    """Test exporting tasks with filters applied."""
    with app.app_context():
        # Update one task to a different status
        tasks_for_bulk[0].status = "in_progress"
        db.session.commit()

        # Export with status filter
        response = authenticated_client.get("/tasks/export?status=in_progress")

        assert response.status_code == 200
        csv_data = response.data.decode("utf-8")

        # Verify CSV structure
        lines = csv_data.split("\n")
        assert "ID,Name,Description,Project,Status" in lines[0]

        # Check if filter worked - if no data, at least header should be there
        # The actual data presence depends on permission model
        assert len(lines) >= 1  # At least header


@pytest.mark.smoke
@pytest.mark.routes
def test_export_button_exists(authenticated_client):
    """Smoke test to verify export button exists on task list."""
    response = authenticated_client.get("/tasks")
    assert response.status_code == 200
    assert b"Export" in response.data or b"export" in response.data
    assert b"/tasks/export" in response.data
