"""Tests for carbcor (uses mocked HTTP responses — no network needed)."""

import math

import numpy as np
import pandas as pd
import pytest
import responses
from bs4 import BeautifulSoup

from carbcor.core import (
    BASE_URL,
    _extract_corrected,
    _map_field_names,
    apply_carbon_corrections,
    get_carbon_correction,
    reset_form_cache,
)

# ── sample HTML fixtures ─────────────────────────────────────────────────────

FORM_HTML = """\
<html><body>
<form action="/result" method="POST">
  <input name="logg" type="text">
  <input name="feh" type="text">
  <input name="cfe" type="text">
  <input type="submit" value="calculate">
</form>
</body></html>
"""

RESULT_HTML = """\
<html><body>
<p>correction = {correction:.2f}</p>
</body></html>
"""


@pytest.fixture(autouse=True)
def _clear_cache():
    """Reset the module-level form cache before every test."""
    reset_form_cache()
    yield
    reset_form_cache()


# ── unit tests ───────────────────────────────────────────────────────────────

class TestMapFieldNames:
    def test_exact_names(self):
        assert _map_field_names(["logg", "feh", "cfe"]) == {
            "logg": "logg", "feh": "feh", "cfe": "cfe",
        }

    def test_positional_fallback(self):
        assert _map_field_names(["x", "y", "z"]) == {
            "logg": "x", "feh": "y", "cfe": "z",
        }

    def test_mixed(self):
        m = _map_field_names(["gravity_logg", "metallicity", "carbon_cfe"])
        assert m["logg"] == "gravity_logg"
        assert m["feh"] == "metallicity"
        assert m["cfe"] == "carbon_cfe"


class TestExtractCorrected:
    def _make(self, html, cfe=1.0):
        soup = BeautifulSoup(html, "html.parser")
        return _extract_corrected(soup, soup.get_text(" ", strip=True), cfe)

    def test_correction_pattern(self):
        assert self._make("<p>correction = 0.85</p>") == pytest.approx(0.85)

    def test_bare_number_in_bold(self):
        assert self._make("<b>1.23</b>") == pytest.approx(1.23)

    def test_returns_none_on_empty(self):
        assert self._make("<p>nothing here</p>") is None


# ── integration tests with mocked HTTP ───────────────────────────────────────

class TestGetCarbonCorrection:
    @responses.activate
    def test_success(self):
        responses.add(responses.GET, BASE_URL, body=FORM_HTML, status=200)
        responses.add(
            responses.POST, f"{BASE_URL}/result",
            body=RESULT_HTML.format(correction=0.85), status=200,
        )
        r = get_carbon_correction(logg=1.0, feh=-3.0, cfe=1.5)
        assert r["status"] == "success"
        assert r["cfe_corrected"] == pytest.approx(2.35, abs=0.01)
        assert r["correction"] == pytest.approx(0.85, abs=0.01)

    @responses.activate
    def test_nan_output_on_connection_error(self):
        responses.add(responses.GET, BASE_URL, body=FORM_HTML, status=200)
        responses.add(
            responses.POST, f"{BASE_URL}/result",
            body=ConnectionError("fail"),
        )
        r = get_carbon_correction(logg=1.0, feh=-3.0, cfe=1.5)
        assert r["status"] == "error"
        assert math.isnan(r["cfe_corrected"])
        assert math.isnan(r["correction"])

    @responses.activate
    def test_http_500(self):
        responses.add(responses.GET, BASE_URL, body=FORM_HTML, status=200)
        responses.add(
            responses.POST, f"{BASE_URL}/result",
            body="Server Error", status=500,
        )
        r = get_carbon_correction(logg=1.0, feh=-3.0, cfe=1.5)
        assert r["status"] == "error"
        assert "HTTP error" in r["message"]


class TestApplyCarbonCorrections:
    @responses.activate
    def test_batch_with_nan_row(self):
        responses.add(responses.GET, BASE_URL, body=FORM_HTML, status=200)
        responses.add(
            responses.POST, f"{BASE_URL}/result",
            body=RESULT_HTML.format(correction=0.5), status=200,
        )
        df = pd.DataFrame({
            "logg": [1.0, float("nan")],
            "feh": [-3.0, -2.0],
            "cfe": [1.5, 0.8],
        })
        out = apply_carbon_corrections(df, delay=0, verbose=False)

        assert out.loc[0, "corr_status"] == "success"
        assert out.loc[0, "cfe_corrected"] == pytest.approx(2.0, abs=0.01)

        assert out.loc[1, "corr_status"] == "error"
        assert np.isnan(out.loc[1, "cfe_corrected"])
        assert np.isnan(out.loc[1, "correction"])

    def test_missing_column_raises(self):
        df = pd.DataFrame({"a": [1], "b": [2], "c": [3]})
        with pytest.raises(ValueError, match="logg"):
            apply_carbon_corrections(df, verbose=False)
