from __future__ import annotations

from dataclasses import dataclass

import numpy as np
import pytest

from cognitive_signal.changepoint.detector import SCALAR_SIGNALS
from cognitive_signal.data.generate_test_data import generate_synthetic_entries
from cognitive_signal.dissonance.detector import DissonanceVector
from cognitive_signal.models.types import JournalEntry, SignalVector
from cognitive_signal.pipeline import DISSONANCE_FIELDS, SignalDetectionPipeline


@dataclass(slots=True)
class StubSignalExtractor:
    """Deterministic Layer-1 stub for stable end-to-end integration tests."""

    def extract(self, entry: JournalEntry) -> SignalVector:
        index = int(entry.entry_id.split("_")[-1]) - 1
        phase = 0 if index < 20 else 1 if index < 40 else 2

        centers = [
            np.concatenate([np.full(128, 2.0), np.zeros(256)]).astype(np.float32),
            np.concatenate([np.zeros(128), np.full(128, 2.0), np.zeros(128)]).astype(np.float32),
            np.concatenate([np.zeros(256), np.full(128, 2.0)]).astype(np.float32),
        ]
        scalar_profiles = [
            (0.65, 0.35, 0.80, 0.25, 0.20, 0.85),
            (0.22, 0.60, 0.45, 0.55, 0.74, 0.45),
            (0.08, 0.24, 0.24, 0.83, 0.34, 0.22),
        ]

        rng = np.random.default_rng(index + 11)
        embedding_noise = rng.normal(0.0, 0.03, size=384).astype(np.float32)
        embedding = centers[phase] + embedding_noise

        base = scalar_profiles[phase]
        noise = rng.normal(0.0, 0.02, size=6)
        values = np.asarray(base) + noise

        return SignalVector(
            embedding=embedding,
            valence=float(np.clip(values[0], -1.0, 1.0)),
            arousal=float(np.clip(values[1], 0.0, 1.0)),
            agency=float(np.clip(values[2], -1.0, 1.0)),
            self_focus=float(np.clip(values[3], 0.0, 1.0)),
            social_orientation=float(np.clip(values[4], 0.0, 1.0)),
            action_ratio=float(np.clip(values[5], 0.0, 1.0)),
        )


def test_full_pipeline_end_to_end_with_stubbed_layer1() -> None:
    entries = generate_synthetic_entries()
    pipeline = SignalDetectionPipeline(signal_extractor=StubSignalExtractor())

    output = pipeline.process_journal(entries)

    assert len(output.journal_entries) == 60
    assert len(output.signal_vectors) == 60
    assert len(output.dissonance_vectors) == 60
    assert all(isinstance(vector, DissonanceVector) for vector in output.dissonance_vectors)
    assert all(vector.embedding.shape == (384,) for vector in output.signal_vectors)

    expected_temporal = set(SCALAR_SIGNALS) | {
        f"dissonance_{field}" for field in DISSONANCE_FIELDS
    }
    assert set(output.temporal_signals.keys()) == expected_temporal
    assert all(len(output.temporal_signals[key]) == 60 for key in expected_temporal)

    assert output.semantic_motion.reduced_embeddings.shape[0] == 60
    assert len(output.segmentation.state_sequence) == 60
    assert len(output.state_labels) >= 1
    assert len(output.changepoints.merged_changepoints) >= 1
    assert len(output.phases) >= 1

    assert output._raw_observations is not None
    assert output._raw_observations.shape == (60, 11)
    assert output._raw_processing_seconds is not None
    assert output._raw_processing_seconds >= 0.0


def test_full_pipeline_real_model_smoke() -> None:
    entries = generate_synthetic_entries(num_entries=5)
    try:
        pipeline = SignalDetectionPipeline()
        output = pipeline.process_journal(entries)
    except Exception as exc:
        pytest.skip(f"Real-model integration dependencies unavailable: {exc}")

    assert len(output.signal_vectors) == 5
    assert len(output.dissonance_vectors) == 5
    assert len(output.segmentation.state_sequence) == 5
