from __future__ import annotations

import os
import socket
import subprocess
import sys
import time
from pathlib import Path
import sqlite3
from urllib.error import URLError
from urllib.request import urlopen

import pytest
import pandas as pd

_STREAMLIT = pytest.importorskip("streamlit")


def _find_free_port() -> int:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.bind(("127.0.0.1", 0))
        return int(sock.getsockname()[1])


def _wait_for_http_ok(url: str, timeout_s: float = 15.0) -> bool:
    deadline = time.time() + timeout_s
    last_error: Exception | None = None
    while time.time() < deadline:
        try:
            with urlopen(url, timeout=1.5) as response:
                if response.status == 200:
                    return True
        except (OSError, URLError) as exc:
            last_error = exc
            time.sleep(0.25)
    if last_error is not None:
        raise AssertionError(f"streamlit app did not become ready: {last_error}") from last_error
    return False


@pytest.mark.parametrize(
    "example_name",
    [
        "pipeline_yaml_config_demo.py",
        "ui_account_config.py",
        "ui_csv_import.py",
        "ui_jinja_templates.py",
        "ui_markdown_showcase.py",
        "ui_orders.py",
        "ui_reconciliation.py",
        "ui_users.py",
    ],
)
def test_streamlit_examples_start_and_serve_http(example_name: str):
    example_path = Path(__file__).resolve().parents[1] / "examples" / example_name
    port = _find_free_port()
    env = os.environ.copy()
    env["STREAMLIT_BROWSER_GATHER_USAGE_STATS"] = "false"
    artifact_dir = Path.cwd() / ".streamlit_smoke_artifacts"
    artifact_dir.mkdir(exist_ok=True)
    env["SOIGIA_CONFIG_PATH"] = str((artifact_dir / f"{Path(example_name).stem}.config.yaml").resolve())
    stem = Path(example_name).stem
    stdout_path = artifact_dir / f"{stem}-{port}.stdout.log"
    stderr_path = artifact_dir / f"{stem}-{port}.stderr.log"

    stdout_handle = stdout_path.open("w", encoding="utf-8")
    stderr_handle = stderr_path.open("w", encoding="utf-8")
    proc = subprocess.Popen(
        [
            sys.executable,
            "-m",
            "streamlit",
            "run",
            str(example_path),
            "--server.headless",
            "true",
            "--browser.gatherUsageStats",
            "false",
            "--server.address",
            "127.0.0.1",
            "--server.port",
            str(port),
        ],
        stdout=stdout_handle,
        stderr=stderr_handle,
        env=env,
    )
    try:
        _wait_for_http_ok(f"http://127.0.0.1:{port}")
    finally:
        if proc.poll() is None:
            proc.terminate()
            try:
                proc.wait(timeout=10)
            except subprocess.TimeoutExpired:
                proc.kill()
                proc.wait(timeout=10)
        stdout_handle.close()
        stderr_handle.close()
        stdout_text = stdout_path.read_text(encoding="utf-8", errors="replace")
        stderr_text = stderr_path.read_text(encoding="utf-8", errors="replace")
        stdout_path.unlink(missing_ok=True)
        stderr_path.unlink(missing_ok=True)

    assert proc.returncode is not None
    assert "Traceback" not in stdout_text
    assert "Traceback" not in stderr_text


def test_pipeline_final_template_runs():
    example_path = Path(__file__).resolve().parents[1] / "examples" / "pipeline_final_template.py"
    proc = subprocess.run([sys.executable, "-m", "examples.pipeline_final_template"], capture_output=True, text=True, timeout=20)

    assert proc.returncode == 0
    dump_dir = Path.cwd() / "data" / "SalesPipeline"
    assert (dump_dir / "01_normalize.csv").exists()
    assert (dump_dir / "02_enrich.csv").exists()
    assert (dump_dir / "03_summarize.csv").exists()
    csv_path = dump_dir / "SalesPipeline.csv"
    assert csv_path.exists()
    db_path = dump_dir / "SalesPipeline.sqlite3"
    assert db_path.exists()
    with sqlite3.connect(db_path) as conn:
        row_count = conn.execute("select count(*) from final_data").fetchone()[0]
    assert row_count == 2
    parquet_path = dump_dir / "SalesPipeline.parquet"
    assert parquet_path.exists()
    parquet_df = pd.read_parquet(parquet_path)
    assert parquet_df.shape[0] == 2
    summary_path = dump_dir / "pipeline_summary.md"
    summary_text = summary_path.read_text(encoding="utf-8", errors="replace")
    assert "# Pipeline Run Summary" in summary_text
    assert "| # | Step | Status | Rows | Added Columns | Removed Columns |" in summary_text
    assert "| 01 | normalize | success |" in summary_text
    assert "| 02 | enrich | success |" in summary_text
    assert "| 03 | summarize | success |" in summary_text
    assert "## Model" in summary_text
    assert "CSV Path" in summary_text


def test_pipeline_yaml_config_demo_runs():
    example_path = Path(__file__).resolve().parents[1] / "examples" / "pipeline_yaml_config_demo.py"
    proc = subprocess.run([sys.executable, "-m", "examples.pipeline_yaml_config_demo"], capture_output=True, text=True, timeout=20)

    assert proc.returncode == 0
    dump_dir = Path.cwd() / "data" / "SalesYamlPipeline"
    summary_path = dump_dir / "pipeline_summary.md"
    assert summary_path.exists()
    summary_text = summary_path.read_text(encoding="utf-8", errors="replace")
    assert "sales-yaml-demo" in summary_text
    assert "trim_customers" not in summary_text
