import logging
import json
from pathlib import Path

import pytest

from mnemosynecore import mattermost


class _FakePosts:
    def __init__(self, should_fail=False):
        self.should_fail = should_fail
        self.calls = []

    def create_post(self, options):
        if self.should_fail:
            raise RuntimeError("boom")
        self.calls.append(options)


class _FakeDriver:
    def __init__(self, cfg, should_fail=False):
        self.cfg = cfg
        self.posts = _FakePosts(should_fail=should_fail)
        self.logged_in = False

    def login(self):
        self.logged_in = True


class _FakeFiles:
    def __init__(self):
        self.calls = []

    def upload_file(self, channel_id, files):
        self.calls.append((channel_id, files))
        return {"file_infos": [{"id": "FILE1"}]}


class _FakeDriverWithFiles:
    def __init__(self):
        self.files = _FakeFiles()
        self.posts = _FakePosts()


def test_get_mattermost_driver_from_json(monkeypatch):
    monkeypatch.setattr(mattermost, "Driver", _FakeDriver)
    raw = '{"host":"h","password":"t","schema":"http","port":8065,"basepath":"/api/v4"}'
    drv = mattermost.get_mattermost_driver(raw)
    assert drv.logged_in is True
    assert drv.cfg["url"] == "h"
    assert drv.cfg["token"] == "t"


def test_get_mattermost_driver_invalid_json():
    with pytest.raises(ValueError, match="Неверный формат JSON"):
        mattermost.get_mattermost_driver("{bad")


def test_get_mattermost_driver_from_conn(monkeypatch):
    monkeypatch.setattr(mattermost, "un_conn", lambda bot_id, conn_type: ("OK", bot_id, conn_type))
    assert mattermost.get_mattermost_driver("MM_CONN") == ("OK", "MM_CONN", "mattermost")


def test_send_message_success(monkeypatch):
    drv = _FakeDriver({})
    monkeypatch.setattr(mattermost, "get_mattermost_driver", lambda bot_id: drv)
    logs = []
    monkeypatch.setattr(logging, "info", lambda msg, cid: logs.append((msg, cid)))

    mattermost.send_message(channel_id="CH", bot_id="BOT", text="  hi  ")

    assert drv.posts.calls == [{"channel_id": "CH", "message": "hi"}]
    assert logs and logs[0][1] == "CH"


def test_post_message_returns_post_id(monkeypatch):
    class _Posts:
        def __init__(self):
            self.calls = []

        def create_post(self, options):
            self.calls.append(options)
            return {"id": "P1"}

    class _Driver:
        def __init__(self):
            self.posts = _Posts()

    drv = _Driver()
    monkeypatch.setattr(mattermost, "get_mattermost_driver", lambda bot_id: drv)

    out = mattermost.post_message(
        channel_id="CH",
        bot_id="BOT",
        text="hello",
        root_id="ROOT1",
        silent=True,
    )
    assert out["post_id"] == "P1"
    assert drv.posts.calls[0]["root_id"] == "ROOT1"


def test_send_message_reraises(monkeypatch):
    drv = _FakeDriver({}, should_fail=True)
    monkeypatch.setattr(mattermost, "get_mattermost_driver", lambda bot_id: drv)
    with pytest.raises(RuntimeError, match="boom"):
        mattermost.send_message(channel_id="CH", bot_id="BOT", text="x")


def test_send_message_test_success(monkeypatch):
    monkeypatch.setattr(mattermost, "Driver", _FakeDriver)
    monkeypatch.setattr(
        __import__("mnemosynecore.vault.client", fromlist=["get_secret_test"]),
        "get_secret_test",
        lambda bot_id, dir_path=None: {"host": "h", "password": "t"},
    )
    mattermost.send_message_test(channel_id="C1", bot_id="B1", text="  test ", silent=True)


def test_send_file_test_supports_creds_path(monkeypatch, tmp_path: Path):
    creds_file = tmp_path / "mm_creds.json"
    creds_file.write_text('{"host":"h","password":"t"}', encoding="utf-8")

    captured = {}
    monkeypatch.setattr(
        mattermost,
        "send_file",
        lambda **kwargs: captured.update(kwargs) or {"file_id": "OK"},
    )

    out = mattermost.send_file_test(
        channel_id="CH",
        bot_id="MM_CONN",
        file_path="/tmp/report.csv",
        creds_path=str(creds_file),
        silent=True,
    )
    assert out["file_id"] == "OK"
    assert json.loads(captured["bot_id"])["host"] == "h"


def test_send_file_bytes(monkeypatch):
    drv = _FakeDriverWithFiles()
    monkeypatch.setattr(mattermost, "get_mattermost_driver", lambda bot_id: drv)

    out = mattermost.send_file_bytes(
        channel_id="CH",
        bot_id="BOT",
        file_name="report.csv",
        file_bytes=b"a,b\n1,2\n",
        text="daily",
        silent=True,
    )

    assert out["file_id"] == "FILE1"
    assert drv.files.calls
    assert drv.posts.calls[0]["file_ids"] == ["FILE1"]


def test_send_file_bytes_to_thread(monkeypatch):
    drv = _FakeDriverWithFiles()
    monkeypatch.setattr(mattermost, "get_mattermost_driver", lambda bot_id: drv)

    out = mattermost.send_file_bytes(
        channel_id="CH",
        bot_id="BOT",
        file_name="report.csv",
        file_bytes=b"1,2",
        root_id="ROOT-X",
        silent=True,
    )
    assert out["root_id"] == "ROOT-X"
    assert drv.posts.calls[0]["root_id"] == "ROOT-X"


def test_send_file_bytes_validates(monkeypatch):
    monkeypatch.setattr(mattermost, "get_mattermost_driver", lambda bot_id: _FakeDriverWithFiles())
    with pytest.raises(ValueError):
        mattermost.send_file_bytes(
            channel_id="CH",
            bot_id="BOT",
            file_name="",
            file_bytes=b"x",
        )
    with pytest.raises(TypeError):
        mattermost.send_file_bytes(
            channel_id="CH",
            bot_id="BOT",
            file_name="a.txt",
            file_bytes="not-bytes",
        )


def test_send_file(monkeypatch, tmp_path: Path):
    path = tmp_path / "f.txt"
    path.write_text("hello", encoding="utf-8")
    captured = {}

    monkeypatch.setattr(
        mattermost,
        "send_file_bytes",
        lambda **kwargs: captured.update(kwargs) or {"file_id": "X", "post_id": "P"},
    )

    out = mattermost.send_file(
        channel_id="CH",
        bot_id="BOT",
        file_path=str(path),
        text="t",
    )
    assert out["file_id"] == "X"
    assert captured["file_name"] == "f.txt"
    assert captured["file_bytes"] == b"hello"


def test_send_file_not_found():
    with pytest.raises(FileNotFoundError):
        mattermost.send_file(channel_id="CH", bot_id="BOT", file_path="/no/such/file.txt")


def test_send_files(monkeypatch):
    calls = []
    monkeypatch.setattr(
        mattermost,
        "send_file",
        lambda **kwargs: calls.append(kwargs["file_path"]) or {"file_id": "X"},
    )

    out = mattermost.send_files(
        channel_id="CH",
        bot_id="BOT",
        file_paths=["a.txt", "b.txt"],
        silent=True,
    )
    assert len(out) == 2
    assert calls == ["a.txt", "b.txt"]


def test_send_dataframe_as_csv(monkeypatch):
    import pandas as pd

    captured = {}
    monkeypatch.setattr(
        mattermost,
        "send_file_bytes",
        lambda **kwargs: captured.update(kwargs) or {"file_id": "X"},
    )
    df = pd.DataFrame([{"a": 1, "b": 2}])

    out = mattermost.send_dataframe_as_csv(
        channel_id="CH",
        bot_id="BOT",
        df=df,
        silent=True,
    )
    assert out["file_id"] == "X"
    assert captured["mime_type"] == "text/csv"
    assert b"a,b" in captured["file_bytes"]


def test_send_dataframe_preview(monkeypatch):
    import pandas as pd

    captured = {}
    monkeypatch.setattr(
        mattermost,
        "send_message",
        lambda **kwargs: captured.update(kwargs),
    )
    df = pd.DataFrame([{"a": 1, "b": 2}, {"a": 3, "b": 4}])
    mattermost.send_dataframe_preview(
        channel_id="CH",
        bot_id="BOT",
        df=df,
        title="preview",
        silent=True,
    )
    assert captured["channel_id"] == "CH"
    assert "preview" in captured["text"]
    assert "rows=2" in captured["text"]


def test_send_thread_message(monkeypatch):
    captured = {}
    monkeypatch.setattr(
        mattermost,
        "post_message",
        lambda **kwargs: captured.update(kwargs) or {"post_id": "P1", "root_id": kwargs["root_id"]},
    )

    out = mattermost.send_thread_message(
        channel_id="CH",
        bot_id="BOT",
        root_id="ROOT1",
        text="thread text",
    )
    assert out["root_id"] == "ROOT1"
    assert captured["root_id"] == "ROOT1"


def test_send_thread_messages_continue_on_error(monkeypatch):
    calls = {"i": 0}

    def fake_send_thread_message(**kwargs):
        calls["i"] += 1
        if calls["i"] == 2:
            raise RuntimeError("boom")
        return {"post_id": f"P{calls['i']}", "root_id": kwargs["root_id"]}

    monkeypatch.setattr(mattermost, "send_thread_message", fake_send_thread_message)

    out = mattermost.send_thread_messages(
        channel_id="CH",
        bot_id="BOT",
        root_id="ROOT1",
        messages=["m1", "m2", "m3"],
        continue_on_error=True,
    )
    assert len(out["sent"]) == 2
    assert len(out["failed"]) == 1


def test_send_thread_message_test_uses_test_bot(monkeypatch):
    monkeypatch.setattr(mattermost, "_resolve_test_bot_id", lambda **kwargs: '{"host":"h","password":"t"}')
    captured = {}
    monkeypatch.setattr(
        mattermost,
        "send_thread_message",
        lambda **kwargs: captured.update(kwargs) or {"post_id": "P1", "root_id": kwargs["root_id"]},
    )
    out = mattermost.send_thread_message_test(
        channel_id="CH",
        bot_id="MM_CONN",
        root_id="ROOT2",
        text="ok",
    )
    assert out["root_id"] == "ROOT2"
    assert captured["bot_id"].startswith("{")
