import os
import tempfile
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from dracs.commands import (
    TSR_DIR,
    _scan_tsr_entries,
    _webapp_session,
    tsr_download,
    tsr_generate,
    tsr_list,
    tsr_status,
)
from dracs.db import db_initialize, upsert_system
from dracs.exceptions import DatabaseError, DracsError


@pytest.fixture
def tsr_db():
    fd, path = tempfile.mkstemp(suffix=".db")
    os.close(fd)
    db_initialize(path)
    upsert_system(
        path,
        "TAG001",
        "server01.example.com",
        "R660",
        "7.0.0",
        "2.1.0",
        "Jan 1, 2027",
        1893456000,
    )
    yield path
    if os.path.exists(path):
        os.unlink(path)


@pytest.fixture
def tsr_dir(tmp_path):
    host_dir = tmp_path / "server01.example.com"
    host_dir.mkdir()
    (host_dir / "TSR20260505170637_TAG001.zip").write_bytes(b"fake1")
    (host_dir / "TSR20260501120000_TAG001.zip").write_bytes(b"fake2")
    (host_dir / "TSR20260415080000_TAG001.zip").write_bytes(b"fake3")
    return tmp_path


class TestScanTsrEntries:
    def test_scans_files(self, tsr_dir):
        with patch("dracs.commands.TSR_DIR", str(tsr_dir)):
            entries = _scan_tsr_entries("server01.example.com")
        assert len(entries) == 3
        assert entries[0]["date"] == "2026/05/05 17:06:37"

    def test_sorted_descending(self, tsr_dir):
        with patch("dracs.commands.TSR_DIR", str(tsr_dir)):
            entries = _scan_tsr_entries("server01.example.com")
        dates = [e["date"] for e in entries]
        assert dates == sorted(dates, reverse=True)

    def test_no_host_dir(self, tmp_path):
        with patch("dracs.commands.TSR_DIR", str(tmp_path)):
            entries = _scan_tsr_entries("nonexistent.example.com")
        assert entries == []

    def test_malformed_filename_skipped(self, tmp_path):
        host_dir = tmp_path / "server01.example.com"
        host_dir.mkdir()
        (host_dir / "TSR20260505170637_TAG001.zip").write_bytes(b"fake")
        (host_dir / "TSRbadtime_TAG001.zip").write_bytes(b"fake")
        with patch("dracs.commands.TSR_DIR", str(tmp_path)):
            entries = _scan_tsr_entries("server01.example.com")
        assert len(entries) == 1


class TestTsrList:
    @pytest.mark.asyncio
    async def test_lists_tsrs(self, tsr_db, tsr_dir, capsys):
        with patch("dracs.commands.TSR_DIR", str(tsr_dir)):
            with patch(
                "dracs.commands.socket.getfqdn", return_value="dracs.example.com"
            ):
                await tsr_list("server01.example.com", tsr_db)
        captured = capsys.readouterr()
        assert "Date: 2026/05/05" in captured.out
        assert "TSR" in captured.out

    @pytest.mark.asyncio
    async def test_list_with_last(self, tsr_db, tsr_dir, capsys):
        with patch("dracs.commands.TSR_DIR", str(tsr_dir)):
            with patch(
                "dracs.commands.socket.getfqdn", return_value="dracs.example.com"
            ):
                await tsr_list("server01.example.com", tsr_db, last=1)
        captured = capsys.readouterr()
        assert "2026/05/05" in captured.out
        assert "2026/05/01" not in captured.out

    @pytest.mark.asyncio
    async def test_list_no_tsrs(self, tsr_db, tmp_path, capsys):
        with patch("dracs.commands.TSR_DIR", str(tmp_path)):
            await tsr_list("server01.example.com", tsr_db)
        captured = capsys.readouterr()
        assert "No TSR collections found" in captured.out

    @pytest.mark.asyncio
    async def test_list_host_not_found(self, tsr_db):
        with pytest.raises(DatabaseError, match="not found"):
            await tsr_list("nonexistent.example.com", tsr_db)


class TestTsrDownload:
    @pytest.mark.asyncio
    async def test_downloads_newest(self, tsr_db, tsr_dir, tmp_path, monkeypatch):
        monkeypatch.chdir(tmp_path)
        with patch("dracs.commands.TSR_DIR", str(tsr_dir)):
            await tsr_download("server01.example.com", tsr_db)
        assert (tmp_path / "TSR20260505170637_TAG001.zip").exists()

    @pytest.mark.asyncio
    async def test_download_no_tsrs(self, tsr_db, tmp_path, capsys):
        with patch("dracs.commands.TSR_DIR", str(tmp_path)):
            await tsr_download("server01.example.com", tsr_db)
        captured = capsys.readouterr()
        assert "No TSR collections found" in captured.out

    @pytest.mark.asyncio
    async def test_download_host_not_found(self, tsr_db):
        with pytest.raises(DatabaseError, match="not found"):
            await tsr_download("nonexistent.example.com", tsr_db)


class TestTsrGenerate:
    @pytest.mark.asyncio
    async def test_generate_success(self, tsr_db, capsys):
        mock_sess = MagicMock()
        mock_login_resp = MagicMock()
        mock_login_resp.status_code = 200
        mock_login_resp.json.return_value = {"success": True}

        mock_collect_resp = MagicMock()
        mock_collect_resp.json.return_value = {
            "success": True,
            "message": "TSR initiated for server01.example.com",
        }

        mock_sess.post.side_effect = [mock_login_resp, mock_collect_resp]

        with patch.dict(
            os.environ,
            {"WEBADMIN_USER": "admin", "WEBADMIN_PASSWORD": "secret"},
        ):
            with patch("dracs.commands.requests.Session", return_value=mock_sess):
                with patch(
                    "dracs.commands.socket.getfqdn", return_value="dracs.example.com"
                ):
                    await tsr_generate("server01.example.com", tsr_db)

        captured = capsys.readouterr()
        assert "TSR initiated" in captured.out

    @pytest.mark.asyncio
    async def test_generate_host_not_found(self, tsr_db):
        with pytest.raises(DatabaseError, match="not found"):
            await tsr_generate("nonexistent.example.com", tsr_db)

    @pytest.mark.asyncio
    async def test_generate_missing_credentials(self, tsr_db):
        with patch.dict(os.environ, {}, clear=True):
            with patch("dracs.commands.os.environ.get", return_value=None):
                with pytest.raises(DracsError, match="WEBADMIN_USER"):
                    await tsr_generate("server01.example.com", tsr_db)

    @pytest.mark.asyncio
    async def test_generate_auth_failure(self, tsr_db):
        mock_sess = MagicMock()
        mock_resp = MagicMock()
        mock_resp.status_code = 401
        mock_resp.json.return_value = {"success": False}
        mock_sess.post.return_value = mock_resp

        with patch.dict(
            os.environ,
            {"WEBADMIN_USER": "admin", "WEBADMIN_PASSWORD": "wrong"},
        ):
            with patch("dracs.commands.requests.Session", return_value=mock_sess):
                with patch(
                    "dracs.commands.socket.getfqdn", return_value="dracs.example.com"
                ):
                    with pytest.raises(DracsError, match="authenticate"):
                        await tsr_generate("server01.example.com", tsr_db)


class TestTsrStatus:
    @pytest.mark.asyncio
    async def test_status_running(self, tsr_db, capsys):
        mock_sess = MagicMock()
        mock_login_resp = MagicMock()
        mock_login_resp.status_code = 200
        mock_login_resp.json.return_value = {"success": True}

        mock_status_resp = MagicMock()
        mock_status_resp.json.return_value = {
            "success": True,
            "state": "running",
            "percent_complete": "45",
        }

        mock_sess.post.side_effect = [mock_login_resp, mock_status_resp]

        with patch.dict(
            os.environ,
            {"WEBADMIN_USER": "admin", "WEBADMIN_PASSWORD": "secret"},
        ):
            with patch("dracs.commands.requests.Session", return_value=mock_sess):
                with patch(
                    "dracs.commands.socket.getfqdn", return_value="dracs.example.com"
                ):
                    await tsr_status("server01.example.com", tsr_db)

        captured = capsys.readouterr()
        assert "TSR Collection in progress: 45% Completed." in captured.out

    @pytest.mark.asyncio
    async def test_status_none(self, tsr_db, capsys):
        mock_sess = MagicMock()
        mock_login_resp = MagicMock()
        mock_login_resp.status_code = 200
        mock_login_resp.json.return_value = {"success": True}

        mock_status_resp = MagicMock()
        mock_status_resp.json.return_value = {
            "success": True,
            "state": "none",
        }

        mock_sess.post.side_effect = [mock_login_resp, mock_status_resp]

        with patch.dict(
            os.environ,
            {"WEBADMIN_USER": "admin", "WEBADMIN_PASSWORD": "secret"},
        ):
            with patch("dracs.commands.requests.Session", return_value=mock_sess):
                with patch(
                    "dracs.commands.socket.getfqdn", return_value="dracs.example.com"
                ):
                    await tsr_status("server01.example.com", tsr_db)

        captured = capsys.readouterr()
        assert "No TSR Collection in progress." in captured.out

    @pytest.mark.asyncio
    async def test_status_host_not_found(self, tsr_db):
        with pytest.raises(DatabaseError, match="not found"):
            await tsr_status("nonexistent.example.com", tsr_db)

    @pytest.mark.asyncio
    async def test_status_api_failure(self, tsr_db):
        mock_sess = MagicMock()
        mock_login_resp = MagicMock()
        mock_login_resp.status_code = 200
        mock_login_resp.json.return_value = {"success": True}

        mock_status_resp = MagicMock()
        mock_status_resp.json.return_value = {
            "success": False,
            "message": "Connection timeout",
        }

        mock_sess.post.side_effect = [mock_login_resp, mock_status_resp]

        with patch.dict(
            os.environ,
            {"WEBADMIN_USER": "admin", "WEBADMIN_PASSWORD": "secret"},
        ):
            with patch("dracs.commands.requests.Session", return_value=mock_sess):
                with patch(
                    "dracs.commands.socket.getfqdn", return_value="dracs.example.com"
                ):
                    with pytest.raises(DracsError, match="TSR status check failed"):
                        await tsr_status("server01.example.com", tsr_db)


class TestTsrGenerateFailure:
    @pytest.mark.asyncio
    async def test_generate_api_failure(self, tsr_db):
        mock_sess = MagicMock()
        mock_login_resp = MagicMock()
        mock_login_resp.status_code = 200
        mock_login_resp.json.return_value = {"success": True}

        mock_collect_resp = MagicMock()
        mock_collect_resp.json.return_value = {
            "success": False,
            "message": "sshpass command not found",
        }

        mock_sess.post.side_effect = [mock_login_resp, mock_collect_resp]

        with patch.dict(
            os.environ,
            {"WEBADMIN_USER": "admin", "WEBADMIN_PASSWORD": "secret"},
        ):
            with patch("dracs.commands.requests.Session", return_value=mock_sess):
                with patch(
                    "dracs.commands.socket.getfqdn", return_value="dracs.example.com"
                ):
                    with pytest.raises(DracsError, match="TSR generation failed"):
                        await tsr_generate("server01.example.com", tsr_db)
