"""
Test suite for route/endpoint testing.
Tests all major routes and API endpoints.
"""

import pytest
from datetime import datetime, timedelta, date
from decimal import Decimal


# ============================================================================
# Smoke Tests - Critical Routes
# ============================================================================


@pytest.mark.smoke
@pytest.mark.routes
def test_health_check(client):
    """Test health check endpoint - critical for deployment."""
    response = client.get("/_health")
    assert response.status_code == 200
    data = response.get_json()
    assert data["status"] == "healthy"


@pytest.mark.smoke
@pytest.mark.routes
def test_login_page_accessible(client):
    """Test that login page is accessible."""
    response = client.get("/login")
    assert response.status_code == 200


@pytest.mark.smoke
@pytest.mark.routes
def test_static_files_accessible(client):
    """Test that static files can be accessed."""
    # Test CSS
    response = client.get("/static/css/style.css")
    # 200 if exists, 404 if not - both are acceptable
    assert response.status_code in [200, 404]


# ============================================================================
# Authentication Routes
# ============================================================================


@pytest.mark.unit
@pytest.mark.routes
def test_protected_route_redirects_to_login(client):
    """Test that protected routes redirect unauthenticated users."""
    response = client.get("/dashboard", follow_redirects=False)
    assert response.status_code == 302
    assert "/login" in response.location or "login" in response.location.lower()


@pytest.mark.unit
@pytest.mark.routes
def test_dashboard_accessible_when_authenticated(authenticated_client):
    """Test that dashboard is accessible for authenticated users."""
    response = authenticated_client.get("/dashboard")
    assert response.status_code == 200


@pytest.mark.unit
@pytest.mark.routes
def test_logout_route(authenticated_client):
    """Test logout functionality."""
    response = authenticated_client.get("/logout", follow_redirects=False)
    assert response.status_code in [302, 200]  # Redirect after logout


# ============================================================================
# Timer Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_start_timer_api(authenticated_client, project, app):
    """Test starting a timer via API."""
    with app.app_context():
        response = authenticated_client.post("/api/timer/start", json={"project_id": project.id})

        # Accept both 200 and 201 as valid responses
        assert response.status_code in [200, 201]


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_stop_timer_api(authenticated_client, active_timer, app):
    """Test stopping a timer via session API (POST /api/timer/stop)."""
    with app.app_context():
        response = authenticated_client.post("/api/timer/stop")
        assert response.status_code == 200
        data = response.get_json()
        assert data.get("success") is True


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_get_active_timer(authenticated_client, active_timer, app):
    """Test getting active timer status (GET /api/timer/status)."""
    with app.app_context():
        response = authenticated_client.get("/api/timer/status")
        assert response.status_code == 200
        data = response.get_json()
        assert data.get("active") is True
        assert data.get("timer") is not None


# ============================================================================
# Project Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_projects_list_page(authenticated_client):
    """Test projects list page."""
    response = authenticated_client.get("/projects")
    assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_projects_create_page_contains_client_modal_trigger(admin_authenticated_client):
    """Projects create page should contain inline client creation trigger."""
    response = admin_authenticated_client.get("/projects/create")
    assert response.status_code == 200
    html = response.get_data(as_text=True)
    assert 'id="openCreateClientModal"' in html


@pytest.mark.integration
@pytest.mark.routes
def test_create_project_post_does_not_500_and_logs_activity(admin_authenticated_client, test_client, app):
    """Regression: creating a project should not 500 due to project.client being a string property."""
    from app import db
    from app.models import Project, Activity

    with app.app_context():
        name = "Project Name test"
        resp = admin_authenticated_client.post(
            "/projects/create",
            data={
                "name": name,
                "client_id": str(test_client.id),
                "description": "Created via test",
                "billable": "",
                "hourly_rate": "",
                "billing_ref": "",
                "budget_amount": "",
                "budget_threshold_percent": "80",
                "code": "",
            },
            follow_redirects=False,
        )

        # On success we redirect to the project page
        assert resp.status_code in (302, 303)

        created = Project.query.filter_by(name=name).order_by(Project.id.desc()).first()
        assert created is not None
        assert created.client_id == test_client.id

        # Ensure we wrote the activity entry with a description that includes the client name
        db.session.expire_all()
        activity = (
            Activity.query.filter_by(entity_type="project", entity_id=created.id, action="created")
            .order_by(Activity.id.desc())
            .first()
        )
        assert activity is not None
        assert test_client.name in (activity.description or "")


@pytest.mark.integration
@pytest.mark.routes
def test_project_create_page(admin_authenticated_client):
    """Test project creation page (requires create_projects permission)."""
    response = admin_authenticated_client.get("/projects/create")
    assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_project_detail_page(authenticated_client, project, app):
    """Test project detail page."""
    with app.app_context():
        response = authenticated_client.get(f"/projects/{project.id}")
        assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_create_project_api(client_with_token, test_client, app):
    """Test creating a project via API v1 (Bearer token)."""
    response = client_with_token.post(
        "/api/v1/projects",
        json={
            "name": "API Test Project",
            "client_id": test_client.id,
            "description": "Created via API test",
            "billable": True,
            "hourly_rate": 85.00,
        },
    )
    assert response.status_code == 201
    data = response.get_json()
    assert data.get("project", {}).get("name") == "API Test Project"


@pytest.mark.integration
@pytest.mark.routes
def test_edit_project_description(admin_authenticated_client, project, app):
    """Test that project description changes are saved correctly."""
    from app.models import Project
    from app import db

    with app.app_context():
        # Get the project ID
        project_id = project.id

        # Verify initial description
        initial_description = project.description or ""

        # New description to test
        new_description = "This is an updated project description with markdown **bold** and *italic* text."

        # POST to edit project with updated description
        response = admin_authenticated_client.post(
            f"/projects/{project_id}/edit",
            data={
                "name": project.name,
                "client_id": project.client_id,
                "description": new_description,
                "billable": "on" if project.billable else "",
                "hourly_rate": str(project.hourly_rate) if project.hourly_rate else "",
                "billing_ref": project.billing_ref or "",
                "code": project.code or "",
                "budget_amount": str(project.budget_amount) if project.budget_amount else "",
                "budget_threshold_percent": str(project.budget_threshold_percent or 80),
            },
            follow_redirects=False,
        )

        # Should redirect on success
        assert response.status_code == 302

        # Verify the description was saved in the database
        db.session.expire_all()  # Clear session cache
        # Query fresh from database instead of refreshing fixture object
        updated_project = Project.query.get(project_id)
        assert updated_project is not None
        assert updated_project.description == new_description
        assert updated_project.description != initial_description


@pytest.mark.smoke
@pytest.mark.routes
def test_project_edit_page_has_markdown_editor(admin_authenticated_client, project):
    """Smoke test: Verify project edit page loads with markdown editor."""
    response = admin_authenticated_client.get(f"/projects/{project.id}/edit")
    assert response.status_code == 200

    html = response.get_data(as_text=True)

    # Verify the description textarea is present
    assert 'id="description"' in html
    assert 'name="description"' in html

    # Verify markdown editor div is present
    assert 'id="description_editor"' in html

    # Verify ToastUI editor is loaded
    assert "toastui-editor" in html.lower() or "toast.ui" in html.lower()

    # Verify form submit handler is present to sync markdown editor
    assert "descriptionInput.value = mdEditor.getMarkdown()" in html or "getMarkdown()" in html


# ============================================================================
# Client Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_clients_list_page(authenticated_client):
    """Test clients list page."""
    response = authenticated_client.get("/clients")
    assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_client_detail_page(authenticated_client, test_client, app):
    """Test client detail page."""
    with app.app_context():
        response = authenticated_client.get(f"/clients/{test_client.id}")
        assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_edit_client_updates_prepaid_fields(admin_authenticated_client, test_client, app):
    """Ensure editing a client updates prepaid hours fields without errors."""
    from app import db
    from app.models import Client

    with app.app_context():
        client_id = test_client.id

        response = admin_authenticated_client.post(
            f"/clients/{client_id}/edit",
            data={
                "name": test_client.name,
                "description": test_client.description or "",
                "contact_person": test_client.contact_person or "",
                "email": test_client.email or "",
                "phone": test_client.phone or "",
                "address": test_client.address or "",
                "default_hourly_rate": "",
                "prepaid_hours_monthly": "12.5",
                "prepaid_reset_day": "10",
            },
            follow_redirects=False,
        )

        assert response.status_code == 302

        db.session.expire_all()
        # Query fresh from database instead of refreshing fixture object
        updated = Client.query.get(client_id)
        assert updated is not None
        assert updated.prepaid_hours_monthly == Decimal("12.5")
        assert updated.prepaid_reset_day == 10


@pytest.mark.integration
@pytest.mark.routes
def test_edit_client_rejects_negative_prepaid_hours(admin_authenticated_client, test_client, app):
    """Regression test: negative prepaid hours should trigger validation error."""
    from app import db
    from app.models import Client

    with app.app_context():
        client_id = test_client.id
        db.session.expire_all()
        baseline = Client.query.get(client_id)
        baseline_hours = baseline.prepaid_hours_monthly
        baseline_reset_day = baseline.prepaid_reset_day

        response = admin_authenticated_client.post(
            f"/clients/{client_id}/edit",
            data={
                "name": test_client.name,
                "description": test_client.description or "",
                "contact_person": test_client.contact_person or "",
                "email": test_client.email or "",
                "phone": test_client.phone or "",
                "address": test_client.address or "",
                "default_hourly_rate": "",
                "prepaid_hours_monthly": "-1",
                "prepaid_reset_day": "3",
            },
            follow_redirects=False,
        )

        # View should re-render with validation error (200 OK) or redirect back
        # If it redirects, follow it to see the error message
        if response.status_code == 302:
            response = admin_authenticated_client.get(response.location, follow_redirects=True)
        assert response.status_code == 200

        db.session.expire_all()
        updated = Client.query.get(client_id)
        assert updated.prepaid_hours_monthly == baseline_hours
        assert updated.prepaid_reset_day == baseline_reset_day


# ============================================================================
# Reports Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_reports_page(authenticated_client):
    """Test reports page."""
    response = authenticated_client.get("/reports")
    assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_time_report_api(authenticated_client, multiple_time_entries, app):
    """Test analytics hours-by-day API (replaces removed /api/reports/time)."""
    with app.app_context():
        response = authenticated_client.get("/api/analytics/hours-by-day", query_string={"days": 30})

        assert response.status_code == 200
        data = response.get_json()
        assert "labels" in data
        assert "datasets" in data


# ============================================================================
# Analytics Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_analytics_page(authenticated_client):
    """Test analytics dashboard page."""
    response = authenticated_client.get("/analytics")
    assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_dashboard_contains_start_timer_modal(authenticated_client):
    """Dashboard should render Start Timer modal container in new UI."""
    response = authenticated_client.get("/dashboard")
    assert response.status_code == 200
    html = response.get_data(as_text=True)
    assert 'id="startTimerModal"' in html
    assert 'id="openStartTimer"' in html


@pytest.mark.smoke
@pytest.mark.routes
def test_base_layout_has_sidebar_toggle(authenticated_client):
    """Ensure sidebar collapse toggle is present on pages."""
    response = authenticated_client.get("/dashboard")
    assert response.status_code == 200
    html = response.get_data(as_text=True)
    assert 'id="sidebarCollapseBtn"' in html
    assert 'id="mobileSidebarBtn"' in html


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_hours_by_day_api(authenticated_client, multiple_time_entries, app):
    """Test hours by day analytics API."""
    with app.app_context():
        response = authenticated_client.get("/api/analytics/hours-by-day", query_string={"days": 7})

        assert response.status_code == 200
        data = response.get_json()
        assert "labels" in data
        assert "datasets" in data


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_hours_by_project_api(authenticated_client, multiple_time_entries, app):
    """Test hours by project analytics API."""
    with app.app_context():
        response = authenticated_client.get("/api/analytics/hours-by-project", query_string={"days": 7})

        assert response.status_code == 200
        data = response.get_json()
        assert "labels" in data
        assert "datasets" in data


# ============================================================================
# Invoice Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_invoices_list_page(authenticated_client):
    """Test invoices list page."""
    response = authenticated_client.get("/invoices")
    assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_invoice_detail_page(authenticated_client, invoice, app):
    """Test invoice detail page."""
    with app.app_context():
        response = authenticated_client.get(f"/invoices/{invoice.id}")
        assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_invoice_create_page(authenticated_client):
    """Test invoice creation page."""
    response = authenticated_client.get("/invoices/create")
    assert response.status_code == 200


# ============================================================================
# Admin Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_admin_page_requires_admin(authenticated_client):
    """Test that admin pages require admin role."""
    response = authenticated_client.get("/admin", follow_redirects=False)
    # Should redirect or return 403
    assert response.status_code in [302, 403]


@pytest.mark.integration
@pytest.mark.routes
def test_admin_page_accessible_by_admin(admin_authenticated_client):
    """Test that admin pages are accessible by admins."""
    response = admin_authenticated_client.get("/admin")
    assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_admin_users_list(admin_authenticated_client):
    """Test admin users list page."""
    response = admin_authenticated_client.get("/admin/users")
    assert response.status_code == 200


# ============================================================================
# Error Pages
# ============================================================================


@pytest.mark.unit
@pytest.mark.routes
def test_404_error_page(client):
    """Test 404 error page."""
    response = client.get("/this-page-does-not-exist")
    assert response.status_code == 404


# ============================================================================
# API Validation Tests
# ============================================================================


@pytest.mark.integration
@pytest.mark.api
def test_api_requires_authentication(client):
    """Test that API endpoints require authentication."""
    response = client.get("/api/timer/status")
    assert response.status_code in [302, 401, 403]


@pytest.mark.integration
@pytest.mark.api
def test_api_invalid_json(authenticated_client):
    """Test API with invalid JSON."""
    response = authenticated_client.post("/api/timer/start", data="invalid json", content_type="application/json")
    # Should return 400 or 422 for bad request
    assert response.status_code in [400, 422, 500]  # Depending on error handling


# ============================================================================
# Settings Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_settings_page(authenticated_client):
    """Test settings page."""
    response = authenticated_client.get("/settings")
    # Settings might be at different URL
    assert response.status_code in [200, 404]


# ============================================================================
# Task Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_tasks_list_page(authenticated_client):
    """Test tasks list page."""
    response = authenticated_client.get("/tasks")
    assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_task_create_page(authenticated_client, project, app):
    """Test task creation page."""
    with app.app_context():
        response = authenticated_client.get(f"/tasks/create?project_id={project.id}")
        assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
def test_task_detail_page(authenticated_client, task, app):
    """Test task detail page."""
    with app.app_context():
        response = authenticated_client.get(f"/tasks/{task.id}")
        assert response.status_code == 200


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_create_task_api(authenticated_client, project, app):
    """Test creating a task via POST /api/tasks/create."""
    with app.app_context():
        response = authenticated_client.post(
            "/api/tasks/create",
            json={
                "name": "API Test Task",
                "project_id": project.id,
                "description": "Created via API test",
                "priority": "medium",
            },
        )
        assert response.status_code == 201
        data = response.get_json()
        assert data.get("success") is True
        assert data.get("name") == "API Test Task"


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_update_task_status_api_put(authenticated_client, task, app):
    """Test updating task status via API using PUT (current behavior)."""
    with app.app_context():
        response = authenticated_client.put(f"/api/tasks/{task.id}/status", json={"status": "in_progress"})
        assert response.status_code in [200, 400, 403, 404]
        if response.status_code == 200:
            data = response.get_json()
            assert data.get("success") is True
            assert data.get("task", {}).get("status") == "in_progress"


# ============================================================================
# Comment Routes (if they exist)
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_add_comment_api(authenticated_client, task, app):
    """Test adding a comment via API."""
    with app.app_context():
        response = authenticated_client.post(f"/api/comments", json={"task_id": task.id, "content": "Test comment"})
        # May not exist or require different structure
        assert response.status_code in [200, 201, 400, 404, 405]


# ============================================================================
# Time Entry Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_time_entries_page(authenticated_client):
    """Test time entries page."""
    response = authenticated_client.get("/time-entries")
    # May be at different URL or part of dashboard
    assert response.status_code in [200, 404]


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_create_time_entry_api(authenticated_client, project, user, app):
    """Test creating a time entry via API."""
    with app.app_context():
        from datetime import datetime, timedelta

        start_time = datetime.utcnow() - timedelta(hours=2)
        end_time = datetime.utcnow()

        response = authenticated_client.post(
            "/api/time-entries",
            json={
                "project_id": project.id,
                "start_time": start_time.isoformat(),
                "end_time": end_time.isoformat(),
                "notes": "API test entry",
            },
        )
        assert response.status_code in [200, 201, 400, 404]


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_update_time_entry_api(authenticated_client, time_entry, app):
    """Test updating a time entry via API."""
    with app.app_context():
        response = authenticated_client.put(f"/api/time-entries/{time_entry.id}", json={"notes": "Updated notes"})
        assert response.status_code in [200, 400, 404]


@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_delete_time_entry_api(authenticated_client, time_entry, app):
    """Test deleting a time entry via API."""
    with app.app_context():
        response = authenticated_client.delete(f"/api/time-entries/{time_entry.id}")
        assert response.status_code in [200, 204, 404]


# ============================================================================
# User Profile Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_user_profile_page(authenticated_client):
    """Test user profile page."""
    response = authenticated_client.get("/profile")
    # May be at different URL
    assert response.status_code in [200, 404]


@pytest.mark.integration
@pytest.mark.routes
def test_user_settings_page(authenticated_client):
    """Test user settings page."""
    response = authenticated_client.get("/user/settings")
    # May be at different URL
    assert response.status_code in [200, 404]


# ============================================================================
# Export Routes
# ============================================================================


@pytest.mark.integration
@pytest.mark.routes
def test_export_time_entries_csv(authenticated_client, multiple_time_entries, app):
    """Test exporting time entries as CSV."""
    with app.app_context():
        from datetime import datetime, timedelta

        response = authenticated_client.get(
            "/reports/export/csv",
            query_string={
                "start_date": (datetime.utcnow() - timedelta(days=30)).strftime("%Y-%m-%d"),
                "end_date": datetime.utcnow().strftime("%Y-%m-%d"),
            },
        )
        assert response.status_code in [200, 404]


@pytest.mark.integration
@pytest.mark.routes
def test_export_invoice_pdf(authenticated_client, invoice_with_items, app):
    """Test exporting invoice as PDF."""
    with app.app_context():
        invoice, _ = invoice_with_items
        response = authenticated_client.get(f"/invoices/{invoice.id}/pdf")
        # PDF generation might not be available in all environments
        assert response.status_code in [200, 404, 500]
