#!/usr/bin/env python3
"""
Integration tests for API v1 — HRT-29
Sprint 3-4: Refacto API /v1/

Run with:
    cd /home/h3r7/turf_saas
    source venv/bin/activate
    python -m pytest tests/test_api_v1.py -v
"""

import json
import os
import sys
import tempfile
import pytest

# Ensure local modules are importable
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Use a temp file DB for tests (in-memory fails with multiple connections)
_tmp_db = tempfile.NamedTemporaryFile(suffix=".db", delete=False)
_tmp_db.close()
os.environ["TURF_SAAS_DB"] = _tmp_db.name
os.environ["JWT_SECRET_KEY"] = "test-secret-key"

from app_v1 import create_app
from auth_db import init_auth_tables


# ──────────────────────────────────────────────────────────────
# Fixtures
# ──────────────────────────────────────────────────────────────


@pytest.fixture(scope="module")
def app():
    application = create_app()
    application.config["TESTING"] = True
    application.config["JWT_SECRET_KEY"] = "test-secret-key"
    yield application


@pytest.fixture(scope="module")
def client(app):
    return app.test_client()


@pytest.fixture(scope="module")
def auth_tokens(client):
    """Register a user and return tokens for each plan."""
    tokens = {}
    plans = {
        "free": ("free@test.com", "password123"),
        "premium": ("premium@test.com", "password123"),
        "pro": ("pro@test.com", "password123"),
    }

    # Register users
    for plan, (email, pw) in plans.items():
        r = client.post(
            "/api/v1/auth/register",
            json={"email": email, "password": pw},
            content_type="application/json",
        )
        assert r.status_code in (201, 409), f"register failed for {plan}: {r.data}"

    # Manually set plans in DB using direct sqlite (bypass app context issues)
    import sqlite3

    db_path = os.environ.get("TURF_SAAS_DB", "/tmp/test_turf.db")
    conn = sqlite3.connect(db_path)
    for plan, (email, _) in plans.items():
        conn.execute("UPDATE users SET plan = ? WHERE email = ?", (plan, email))
    conn.commit()
    conn.close()

    # Login and collect tokens
    for plan, (email, pw) in plans.items():
        r = client.post(
            "/api/v1/auth/login",
            json={"email": email, "password": pw},
            content_type="application/json",
        )
        assert r.status_code == 200, f"login failed for {plan}: {r.data}"
        data = r.get_json()
        tokens[plan] = data["access_token"]

    return tokens


def auth_header(token: str) -> dict:
    return {"Authorization": f"Bearer {token}"}


# ──────────────────────────────────────────────────────────────
# Health
# ──────────────────────────────────────────────────────────────


class TestHealth:
    def test_health_public(self, client):
        """GET /api/v1/health — no auth required"""
        r = client.get("/api/v1/health")
        assert r.status_code == 200
        data = r.get_json()
        assert data["status"] == "ok"
        assert data["version"] == "1.0"
        assert "timestamp" in data

    def test_health_returns_json(self, client):
        r = client.get("/api/v1/health")
        assert r.content_type.startswith("application/json")


# ──────────────────────────────────────────────────────────────
# Auth
# ──────────────────────────────────────────────────────────────


class TestAuth:
    def test_register_new_user(self, client):
        r = client.post(
            "/api/v1/auth/register",
            json={"email": "new_test@example.com", "password": "strongpass123"},
        )
        assert r.status_code in (201, 409)

    def test_register_short_password(self, client):
        r = client.post(
            "/api/v1/auth/register",
            json={"email": "bad@example.com", "password": "123"},
        )
        assert r.status_code == 400

    def test_register_invalid_email(self, client):
        r = client.post(
            "/api/v1/auth/register",
            json={"email": "notemail", "password": "password123"},
        )
        assert r.status_code == 400

    def test_login_valid(self, client, auth_tokens):
        assert "free" in auth_tokens

    def test_login_wrong_password(self, client):
        r = client.post(
            "/api/v1/auth/login",
            json={"email": "free@test.com", "password": "wrongpassword"},
        )
        assert r.status_code == 401

    def test_protected_without_token(self, client):
        r = client.get("/api/v1/courses/today")
        assert r.status_code == 401


# ──────────────────────────────────────────────────────────────
# Courses
# ──────────────────────────────────────────────────────────────


class TestCourses:
    def test_today_requires_auth(self, client):
        r = client.get("/api/v1/courses/today")
        assert r.status_code == 401

    def test_today_with_auth(self, client, auth_tokens):
        r = client.get(
            "/api/v1/courses/today",
            headers=auth_header(auth_tokens["free"]),
        )
        assert r.status_code == 200
        data = r.get_json()
        assert data["status"] == "ok"
        assert "courses" in data
        assert "pagination" in data
        assert "date" in data

    def test_today_pagination(self, client, auth_tokens):
        r = client.get(
            "/api/v1/courses/today?limit=5&offset=0",
            headers=auth_header(auth_tokens["free"]),
        )
        assert r.status_code == 200
        data = r.get_json()
        assert data["pagination"]["limit"] == 5
        assert data["pagination"]["offset"] == 0

    def test_today_filter_all(self, client, auth_tokens):
        r = client.get(
            "/api/v1/courses/today?filter=all",
            headers=auth_header(auth_tokens["free"]),
        )
        assert r.status_code == 200

    def test_course_predictions_requires_auth(self, client):
        r = client.get("/api/v1/courses/1-1/predictions")
        assert r.status_code == 401

    def test_course_predictions_invalid_id(self, client, auth_tokens):
        r = client.get(
            "/api/v1/courses/invalid/predictions",
            headers=auth_header(auth_tokens["free"]),
        )
        assert r.status_code == 400

    def test_course_predictions_not_found(self, client, auth_tokens):
        r = client.get(
            "/api/v1/courses/99-99/predictions",
            headers=auth_header(auth_tokens["free"]),
        )
        # 404 expected since DB is empty; 429 if free daily limit already reached in this session
        assert r.status_code in (404, 200, 429)  # 200 if gracefully returns empty


# ──────────────────────────────────────────────────────────────
# Predictions
# ──────────────────────────────────────────────────────────────


class TestPredictions:
    def test_top3_requires_auth(self, client):
        r = client.get("/api/v1/predictions/top3")
        assert r.status_code == 401

    def test_top3_free_allowed(self, client, auth_tokens):
        # Reset daily usage for free user before testing rate-limited endpoint
        import sqlite3

        db_path = os.environ.get("TURF_SAAS_DB", "/tmp/test_turf.db")
        conn = sqlite3.connect(db_path)
        conn.execute(
            "UPDATE users SET daily_usage=0, last_usage_date=NULL WHERE email='free@test.com'"
        )
        conn.commit()
        conn.close()

        r = client.get(
            "/api/v1/predictions/top3",
            headers=auth_header(auth_tokens["free"]),
        )
        assert r.status_code == 200
        data = r.get_json()
        assert data["status"] == "ok"
        assert "top3" in data

    def test_all_requires_premium(self, client, auth_tokens):
        r = client.get(
            "/api/v1/predictions/all",
            headers=auth_header(auth_tokens["free"]),
        )
        assert r.status_code == 403

    def test_all_premium_allowed(self, client, auth_tokens):
        r = client.get(
            "/api/v1/predictions/all",
            headers=auth_header(auth_tokens["premium"]),
        )
        assert r.status_code == 200
        data = r.get_json()
        assert data["status"] == "ok"
        assert "predictions" in data
        assert "pagination" in data

    def test_all_pro_allowed(self, client, auth_tokens):
        r = client.get(
            "/api/v1/predictions/all",
            headers=auth_header(auth_tokens["pro"]),
        )
        assert r.status_code == 200


# ──────────────────────────────────────────────────────────────
# Value Bets
# ──────────────────────────────────────────────────────────────


class TestValueBets:
    def test_requires_auth(self, client):
        r = client.get("/api/v1/valuebets")
        assert r.status_code == 401

    def test_free_forbidden(self, client, auth_tokens):
        r = client.get(
            "/api/v1/valuebets",
            headers=auth_header(auth_tokens["free"]),
        )
        assert r.status_code == 403

    def test_premium_allowed(self, client, auth_tokens):
        r = client.get(
            "/api/v1/valuebets",
            headers=auth_header(auth_tokens["premium"]),
        )
        assert r.status_code == 200
        data = r.get_json()
        assert data["status"] == "ok"
        assert "valuebets" in data
        assert "pagination" in data

    def test_min_odds_filter(self, client, auth_tokens):
        r = client.get(
            "/api/v1/valuebets?min_odds=3.0",
            headers=auth_header(auth_tokens["premium"]),
        )
        assert r.status_code == 200
        data = r.get_json()
        assert data["min_odds"] == 3.0


# ──────────────────────────────────────────────────────────────
# Backtest
# ──────────────────────────────────────────────────────────────


class TestBacktest:
    def test_requires_auth(self, client):
        r = client.get("/api/v1/backtest")
        assert r.status_code == 401

    def test_premium_forbidden(self, client, auth_tokens):
        r = client.get(
            "/api/v1/backtest",
            headers=auth_header(auth_tokens["premium"]),
        )
        assert r.status_code == 403

    def test_pro_allowed(self, client, auth_tokens):
        r = client.get(
            "/api/v1/backtest",
            headers=auth_header(auth_tokens["pro"]),
        )
        assert r.status_code == 200
        data = r.get_json()
        assert data["status"] == "ok"
        assert "summary" in data
        assert "period" in data

    def test_invalid_date_format(self, client, auth_tokens):
        r = client.get(
            "/api/v1/backtest?start=31-12-2025",
            headers=auth_header(auth_tokens["pro"]),
        )
        assert r.status_code == 400


# ──────────────────────────────────────────────────────────────
# Export
# ──────────────────────────────────────────────────────────────


class TestExport:
    def test_requires_auth(self, client):
        r = client.get("/api/v1/export/csv")
        assert r.status_code == 401

    def test_free_forbidden(self, client, auth_tokens):
        r = client.get(
            "/api/v1/export/csv",
            headers=auth_header(auth_tokens["free"]),
        )
        assert r.status_code == 403

    def test_premium_forbidden(self, client, auth_tokens):
        r = client.get(
            "/api/v1/export/csv",
            headers=auth_header(auth_tokens["premium"]),
        )
        assert r.status_code == 403

    def test_pro_allowed_predictions(self, client, auth_tokens):
        r = client.get(
            "/api/v1/export/csv?type=predictions",
            headers=auth_header(auth_tokens["pro"]),
        )
        # 200 (CSV) or 400 if table doesn't exist in test DB
        assert r.status_code in (200, 400)
        if r.status_code == 200:
            assert "text/csv" in r.content_type

    def test_invalid_type(self, client, auth_tokens):
        r = client.get(
            "/api/v1/export/csv?type=invalid",
            headers=auth_header(auth_tokens["pro"]),
        )
        assert r.status_code == 400


# ──────────────────────────────────────────────────────────────
# Metrics
# ──────────────────────────────────────────────────────────────


class TestMetrics:
    def test_requires_auth(self, client):
        r = client.get("/api/v1/metrics")
        assert r.status_code == 401

    def test_free_forbidden(self, client, auth_tokens):
        r = client.get(
            "/api/v1/metrics",
            headers=auth_header(auth_tokens["free"]),
        )
        assert r.status_code == 403

    def test_premium_allowed(self, client, auth_tokens):
        r = client.get(
            "/api/v1/metrics",
            headers=auth_header(auth_tokens["premium"]),
        )
        assert r.status_code == 200
        data = r.get_json()
        assert data["status"] == "ok"
        assert "bet_metrics" in data
        assert "ml_metrics" in data
        assert "period" in data

    def test_days_parameter(self, client, auth_tokens):
        r = client.get(
            "/api/v1/metrics?days=7",
            headers=auth_header(auth_tokens["premium"]),
        )
        assert r.status_code == 200
        data = r.get_json()
        assert data["period"]["days"] == 7

    def test_invalid_days(self, client, auth_tokens):
        r = client.get(
            "/api/v1/metrics?days=abc",
            headers=auth_header(auth_tokens["premium"]),
        )
        assert r.status_code == 400


# ──────────────────────────────────────────────────────────────
# Global error handlers
# ──────────────────────────────────────────────────────────────


class TestErrorHandlers:
    def test_404_returns_json(self, client):
        r = client.get("/api/v1/this-does-not-exist")
        assert r.status_code == 404
        data = r.get_json()
        assert data["code"] == 404

    def test_uniform_error_shape(self, client):
        """All error responses must have {status, message, code}."""
        r = client.get("/api/v1/this-does-not-exist")
        data = r.get_json()
        assert "status" in data
        assert "message" in data
        assert "code" in data


# ──────────────────────────────────────────────────────────────
# Swagger docs
# ──────────────────────────────────────────────────────────────


class TestDocs:
    def test_docs_accessible(self, client):
        r = client.get("/api/v1/docs")
        # flasgger returns a redirect or the UI page
        assert r.status_code in (200, 301, 302)

    def test_apispec_json(self, client):
        r = client.get("/api/v1/apispec.json")
        assert r.status_code == 200
        spec = r.get_json()
        assert spec["swagger"] == "2.0"
        assert "paths" in spec
