"""Smoke test: full Fitbit pipeline.

Requires live credentials in environment:
    FITBIT_CLIENT_ID, FITBIT_CLIENT_SECRET
    and a valid .fitbit_tokens.json
"""

import json
import os
import sqlite3
from datetime import datetime, timezone
from pathlib import Path

import pytest

from byteb4rb1e.homeostat.adapters.fitbit import (
    FitbitAdapter,
    TokenStore,
)
from byteb4rb1e.homeostat.projections.builder import (
    ProjectionBuilder,
)
from byteb4rb1e.homeostat.transformers.fitbit import (
    transform_activities,
    transform_body,
    transform_calories,
    transform_heart_rate,
    transform_sleep,
    transform_steps,
)
from byteb4rb1e.homeostat.warehouse.canonical import (
    CanonicalWarehouse,
)
from byteb4rb1e.homeostat.warehouse.raw import RawWarehouse


pytestmark = pytest.mark.skipif(
    not os.environ.get("FITBIT_CLIENT_ID"),
    reason="FITBIT_CLIENT_ID not set",
)


@pytest.fixture(scope="module")
def adapter():
    return FitbitAdapter(
        client_id=os.environ["FITBIT_CLIENT_ID"],
        client_secret=os.environ["FITBIT_CLIENT_SECRET"],
        token_store=TokenStore(
            path=Path(".fitbit_tokens.json")
        ),
    )


@pytest.fixture(scope="module")
def today():
    return datetime.now().strftime("%Y-%m-%d")


@pytest.fixture(scope="module")
def fetched_at():
    return datetime.now(timezone.utc).isoformat()


@pytest.fixture(scope="module")
def envelopes(adapter, today):
    """Fetch all Fitbit endpoints for today."""
    return {
        "steps": adapter.steps(today),
        "calories": adapter.calories(today),
        "heart_rate": adapter.heart_rate(today),
        "sleep": adapter.sleep(today),
        "body": adapter.body(today),
        "activities": adapter.activities(today),
    }


class TestFitbitFetch:
    def test_steps_envelope(self, envelopes):
        env = envelopes["steps"]
        assert env.source == "fitbit"
        assert "activities-steps" in env.payload

    def test_calories_envelope(self, envelopes):
        env = envelopes["calories"]
        assert "activities-calories" in env.payload

    def test_sleep_envelope(self, envelopes):
        env = envelopes["sleep"]
        assert "sleep" in env.payload

    def test_body_envelope(self, envelopes):
        env = envelopes["body"]
        assert "body" in env.payload

    def test_activities_envelope(self, envelopes):
        env = envelopes["activities"]
        assert "summary" in env.payload


class TestRawWarehouse:
    def test_raw_write_and_read(
        self, tmp_path, envelopes, today
    ):
        wh = RawWarehouse(root=tmp_path)
        for env in envelopes.values():
            wh.append(env, today)

        partitions = list(wh.iter_partitions())
        assert len(partitions) >= 1

        read_back = wh.read_partition("fitbit", today)
        assert len(read_back) == len(envelopes)


class TestCanonicalTransform:
    def test_transform_and_warehouse(
        self, tmp_path, envelopes, today, fetched_at
    ):
        wh = CanonicalWarehouse(root=tmp_path)

        steps = transform_steps(
            envelopes["steps"].payload, today, fetched_at
        )
        wh.append_many("step-snapshot", steps, today)
        assert len(steps) >= 1

        cals = transform_calories(
            envelopes["calories"].payload, today, fetched_at
        )
        wh.append_many("calorie-snapshot", cals, today)
        assert len(cals) >= 1

        am, al, dist, sess = transform_activities(
            envelopes["activities"].payload,
            today, fetched_at,
        )
        wh.append_many(
            "active-minutes-snapshot", am, today
        )
        wh.append_many(
            "activity-level-snapshot", al, today
        )
        wh.append_many("distance-snapshot", dist, today)
        wh.append_many("activity-session", sess, today)

        sessions, stages = transform_sleep(
            envelopes["sleep"].payload, today, fetched_at
        )
        wh.append_many("sleep-session", sessions, today)
        wh.append_many("sleep-stage", stages, today)

        body = transform_body(
            envelopes["body"].payload, today, fetched_at
        )
        wh.append_many("body-measurement", body, today)

        partitions = list(wh.iter_partitions())
        assert len(partitions) >= 3


class TestProjection:
    def test_full_pipeline(
        self, tmp_path, envelopes, today, fetched_at
    ):
        wh = CanonicalWarehouse(root=tmp_path / "data")

        steps = transform_steps(
            envelopes["steps"].payload, today, fetched_at
        )
        wh.append_many("step-snapshot", steps, today)

        cals = transform_calories(
            envelopes["calories"].payload, today, fetched_at
        )
        wh.append_many("calorie-snapshot", cals, today)

        am, al, dist, sess = transform_activities(
            envelopes["activities"].payload,
            today, fetched_at,
        )
        wh.append_many(
            "active-minutes-snapshot", am, today
        )
        wh.append_many(
            "activity-level-snapshot", al, today
        )
        wh.append_many("distance-snapshot", dist, today)
        wh.append_many("activity-session", sess, today)

        sessions, stages = transform_sleep(
            envelopes["sleep"].payload, today, fetched_at
        )
        wh.append_many("sleep-session", sessions, today)
        wh.append_many("sleep-stage", stages, today)

        body = transform_body(
            envelopes["body"].payload, today, fetched_at
        )
        wh.append_many("body-measurement", body, today)

        db = sqlite3.connect(":memory:")
        count = wh.hydrate(db)
        assert count >= 3

        builder = ProjectionBuilder(
            db=db, build_root=tmp_path / "build"
        )
        out = builder.build(today)

        assert (out / "status.json").exists()
        status = json.loads(
            (out / "status.json").read_text()
        )
        assert "vitals" in status
        assert "sleep" in status
        assert "activity" in status
        assert "measurements" in status
        assert status["vitals"]["steps_today"] >= 0

        current = (
            tmp_path / "build" / "projection" / "current"
        )
        assert current.is_symlink()
