"""Tests for myburn.push — payload, formatting, receipts, CLI flow."""
from __future__ import annotations

import json
import os
import sqlite3
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

# Point data dir to a temp location BEFORE importing push/server
_tmpdir = tempfile.mkdtemp()
os.environ["MYBURN_DATA_DIR"] = _tmpdir

from myburn.push import (
    _AUTO_INTERVALS,
    _compute_streak,
    _fmt_tokens,
    _load_full_receipts,
    _month_name,
    _verify_local_chain,
    build_payload,
    check_auto_push,
    format_dry_run,
    is_first_push,
    list_receipts,
    mark_receipt_synced,
    save_receipt,
)
from myburn.server import DB_PATH, _get_db, _init_db


def _seed_db():
    """Create tables and insert known test data."""
    _init_db()
    conn = _get_db()

    # daily_usage: 3 days, 2 models
    conn.executemany(
        "INSERT INTO daily_usage (date, project, model_name, input_tokens, output_tokens, "
        "cache_creation_tokens, cache_read_tokens, cost) VALUES (?,?,?,?,?,?,?,?)",
        [
            ("2026-02-01", "__all__", "claude-opus-4-6", 1000, 500, 200, 50000, 45.00),
            ("2026-02-01", "__all__", "claude-haiku-4-5", 2000, 300, 100, 30000, 5.00),
            ("2026-02-02", "__all__", "claude-opus-4-6", 1500, 800, 300, 60000, 55.00),
            ("2026-02-03", "__all__", "claude-opus-4-6", 500, 200, 100, 20000, 30.00),
            # Different month — should NOT appear in Feb payload
            ("2026-01-15", "__all__", "claude-opus-4-6", 900, 400, 150, 40000, 38.00),
            # Different project — should NOT appear (we only query __all__)
            ("2026-02-01", "proj-a", "claude-opus-4-6", 999, 999, 999, 999, 999.0),
        ],
    )

    # sessions
    conn.executemany(
        "INSERT INTO sessions (session_id, project_path, input_tokens, output_tokens, "
        "cache_creation_tokens, cache_read_tokens, total_tokens, total_cost, last_activity, "
        "models_used, model_breakdowns) VALUES (?,?,?,?,?,?,?,?,?,?,?)",
        [
            ("s1", "/p", 100, 50, 10, 5000, 5160, 20.0, "2026-02-01T10:00:00Z", "[]", "[]"),
            ("s2", "/p", 200, 80, 20, 8000, 8300, 35.0, "2026-02-02T14:00:00Z", "[]", "[]"),
            ("s3", "/p", 50, 20, 5, 2000, 2075, 10.0, "2026-02-03T09:00:00Z", "[]", "[]"),
            # Jan session — should NOT count
            ("s4", "/p", 50, 20, 5, 2000, 2075, 10.0, "2026-01-15T09:00:00Z", "[]", "[]"),
        ],
    )

    # native_turns: 10 turns across 3 days
    turns = [
        # Feb 1: 4 turns (1 YOLO, 1 thinking, 1 error)
        ("file1.jsonl", 0, "2026-02-01T10:00:00Z", "claude-opus-4-6", "default", 120000, 100, 50, 5000, 20, 4.50, 0, 2, '["Read","Bash"]', 0, 0, "proj"),
        ("file1.jsonl", 1, "2026-02-01T10:05:00Z", "claude-opus-4-6", "bypassPermissions", 90000, 80, 40, 4000, 15, 3.20, 1, 1, '["Edit"]', 0, 0, "proj"),
        ("file1.jsonl", 2, "2026-02-01T10:10:00Z", "claude-opus-4-6", "default", 150000, 120, 60, 6000, 25, 5.00, 0, 0, '[]', 1, 0, "proj"),
        ("file1.jsonl", 3, "2026-02-01T10:15:00Z", "claude-haiku-4-5", "default", 30000, 200, 30, 3000, 10, 0.80, 0, 1, '["Grep"]', 0, 0, "proj"),
        # Feb 2: 3 turns (1 YOLO)
        ("file2.jsonl", 0, "2026-02-02T14:00:00Z", "claude-opus-4-6", "default", 180000, 150, 70, 7000, 30, 6.00, 1, 3, '["Read","Edit","Bash"]', 0, 0, "proj"),
        ("file2.jsonl", 1, "2026-02-02T14:10:00Z", "claude-opus-4-6", "bypassPermissions", 60000, 50, 25, 2500, 10, 2.00, 0, 1, '["Write"]', 0, 0, "proj"),
        ("file2.jsonl", 2, "2026-02-02T14:20:00Z", "claude-opus-4-6", "default", 200000, 180, 90, 9000, 35, 7.50, 0, 2, '["Read","Glob"]', 0, 0, "proj"),
        # Feb 3: 3 turns (0 YOLO, 1 thinking)
        ("file3.jsonl", 0, "2026-02-03T09:00:00Z", "claude-opus-4-6", "default", 100000, 90, 45, 4500, 18, 3.80, 1, 2, '["Edit","Bash"]', 0, 0, "proj"),
        ("file3.jsonl", 1, "2026-02-03T09:10:00Z", "claude-opus-4-6", "default", 140000, 110, 55, 5500, 22, 4.60, 0, 1, '["Read"]', 0, 0, "proj"),
        ("file3.jsonl", 2, "2026-02-03T09:20:00Z", "claude-opus-4-6", "default", None, 70, 35, 3500, 14, 2.80, 0, 0, '[]', 0, 0, "proj"),
    ]
    conn.executemany(
        "INSERT INTO native_turns (session_file, turn_index, timestamp, model, permission_mode, "
        "duration_ms, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, "
        "cost, has_thinking, tool_count, tools_used, is_error, is_interrupted, project) "
        "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
        turns,
    )

    conn.commit()
    conn.close()


class TestComputeStreak(unittest.TestCase):
    def test_empty(self):
        self.assertEqual(_compute_streak([]), 0)

    def test_single_day(self):
        self.assertEqual(_compute_streak(["2026-02-01"]), 1)

    def test_consecutive(self):
        self.assertEqual(_compute_streak(["2026-02-01", "2026-02-02", "2026-02-03"]), 3)

    def test_gap(self):
        self.assertEqual(_compute_streak(["2026-02-01", "2026-02-02", "2026-02-05", "2026-02-06"]), 2)

    def test_longest_not_first(self):
        dates = ["2026-02-01", "2026-02-05", "2026-02-06", "2026-02-07", "2026-02-08"]
        self.assertEqual(_compute_streak(dates), 4)

    def test_month_boundary(self):
        self.assertEqual(_compute_streak(["2026-01-31", "2026-02-01"]), 2)


class TestFmtTokens(unittest.TestCase):
    def test_billions(self):
        self.assertEqual(_fmt_tokens(1_500_000_000), "1.5B")

    def test_millions(self):
        self.assertEqual(_fmt_tokens(42_000_000), "42.0M")

    def test_thousands(self):
        self.assertEqual(_fmt_tokens(8_500), "8.5K")

    def test_small(self):
        self.assertEqual(_fmt_tokens(999), "999")

    def test_zero(self):
        self.assertEqual(_fmt_tokens(0), "0")


class TestMonthName(unittest.TestCase):
    def test_feb(self):
        self.assertEqual(_month_name("2026-02"), "February 2026")

    def test_jan(self):
        self.assertEqual(_month_name("2026-01"), "January 2026")

    def test_dec(self):
        self.assertEqual(_month_name("2025-12"), "December 2025")


class TestBuildPayload(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        _seed_db()

    def test_schema_keys(self):
        p = build_payload("2026-02")
        expected_keys = {
            "version", "cliVersion", "month", "generatedAt", "cost", "tokens",
            "sessions", "activeDays", "streak", "peakDay", "models", "days",
            "turns", "tools",
        }
        self.assertEqual(set(p.keys()), expected_keys)

    def test_token_keys(self):
        p = build_payload("2026-02")
        self.assertEqual(set(p["tokens"].keys()), {"total", "input", "output", "cacheRead", "cacheCreation"})

    def test_turn_keys(self):
        p = build_payload("2026-02")
        self.assertEqual(set(p["turns"].keys()), {"total", "yolo", "yoloPct", "thinkingPct", "errorPct", "avgDurationMs"})

    def test_cost_sum(self):
        """Total cost = sum of __all__ rows for Feb only."""
        p = build_payload("2026-02")
        # 45 + 5 + 55 + 30 = 135
        self.assertAlmostEqual(p["cost"], 135.0, places=2)

    def test_tokens_sum(self):
        p = build_payload("2026-02")
        # Input: 1000+2000+1500+500=5000, Output: 500+300+800+200=1800
        # CacheCreation: 200+100+300+100=700, CacheRead: 50000+30000+60000+20000=160000
        self.assertEqual(p["tokens"]["input"], 5000)
        self.assertEqual(p["tokens"]["output"], 1800)
        self.assertEqual(p["tokens"]["cacheCreation"], 700)
        self.assertEqual(p["tokens"]["cacheRead"], 160000)
        self.assertEqual(p["tokens"]["total"], 5000 + 1800 + 700 + 160000)

    def test_sessions_count(self):
        p = build_payload("2026-02")
        self.assertEqual(p["sessions"], 3)  # s1, s2, s3 (not s4 from Jan)

    def test_active_days(self):
        p = build_payload("2026-02")
        self.assertEqual(p["activeDays"], 3)  # Feb 1, 2, 3

    def test_streak(self):
        p = build_payload("2026-02")
        self.assertEqual(p["streak"], 3)  # Feb 1-2-3 consecutive

    def test_peak_day(self):
        p = build_payload("2026-02")
        self.assertEqual(p["peakDay"]["date"], "2026-02-02")  # 55.00 is highest
        self.assertAlmostEqual(p["peakDay"]["cost"], 55.0, places=2)

    def test_models_sorted_by_cost(self):
        p = build_payload("2026-02")
        costs = [m["cost"] for m in p["models"]]
        self.assertEqual(costs, sorted(costs, reverse=True))
        self.assertEqual(len(p["models"]), 2)  # opus and haiku

    def test_days_sorted(self):
        p = build_payload("2026-02")
        dates = [d["date"] for d in p["days"]]
        self.assertEqual(dates, sorted(dates))
        self.assertEqual(len(p["days"]), 3)

    def test_turns_total(self):
        p = build_payload("2026-02")
        self.assertEqual(p["turns"]["total"], 10)

    def test_turns_yolo(self):
        p = build_payload("2026-02")
        self.assertEqual(p["turns"]["yolo"], 2)  # 2 bypassPermissions turns
        self.assertAlmostEqual(p["turns"]["yoloPct"], 20.0, places=1)

    def test_turns_thinking(self):
        p = build_payload("2026-02")
        # has_thinking=1 on turns: index 1 of file1, index 0 of file2, index 0 of file3 = 3
        self.assertAlmostEqual(p["turns"]["thinkingPct"], 30.0, places=1)

    def test_turns_error(self):
        p = build_payload("2026-02")
        # is_error=1 only on file1 turn 2
        self.assertAlmostEqual(p["turns"]["errorPct"], 10.0, places=1)

    def test_turns_avg_duration(self):
        p = build_payload("2026-02")
        # 9 turns with duration_ms (one is None)
        # 120k+90k+150k+30k+180k+60k+200k+100k+140k = 1070000, /9 = 118888.9
        self.assertIsNotNone(p["turns"]["avgDurationMs"])
        self.assertAlmostEqual(p["turns"]["avgDurationMs"], 118889, delta=1)

    def test_tools_counted(self):
        p = build_payload("2026-02")
        # Read: turns 0,4,6,8 = 4; Bash: turns 0,4,7 = 3; Edit: 1,4,7 = 3; Grep: 3; Glob: 6; Write: 5
        self.assertEqual(p["tools"]["Read"], 4)
        self.assertEqual(p["tools"]["Bash"], 3)
        self.assertEqual(p["tools"]["Edit"], 3)

    def test_month_filter(self):
        """January payload should have different data."""
        p = build_payload("2026-01")
        self.assertAlmostEqual(p["cost"], 38.0, places=2)
        self.assertEqual(p["sessions"], 1)  # s4

    def test_empty_month(self):
        p = build_payload("2025-06")
        self.assertEqual(p["cost"], 0)
        self.assertEqual(p["turns"]["total"], 0)

    def test_month_short_form(self):
        """'02' should expand to '2026-02'."""
        p = build_payload("02")
        self.assertEqual(p["month"], "2026-02")
        self.assertGreater(p["cost"], 0)

    def test_version_fields(self):
        p = build_payload("2026-02")
        self.assertEqual(p["version"], "1.0")
        self.assertEqual(p["cliVersion"], "0.1.0")

    def test_no_private_data(self):
        """Payload must NEVER contain code, prompts, file paths, project names."""
        p = build_payload("2026-02")
        payload_str = json.dumps(p)
        self.assertNotIn("proj-a", payload_str)
        self.assertNotIn("file1.jsonl", payload_str)
        self.assertNotIn("file2.jsonl", payload_str)
        self.assertNotIn("file3.jsonl", payload_str)
        self.assertNotIn("/p", payload_str)  # project_path


class TestFormatDryRun(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        # Re-use the seeded DB
        cls.payload = build_payload("2026-02")

    def test_contains_burn(self):
        out = format_dry_run(self.payload)
        self.assertIn("$135.00", out)

    def test_contains_month(self):
        out = format_dry_run(self.payload)
        self.assertIn("February 2026", out)

    def test_contains_privacy_notice(self):
        out = format_dry_run(self.payload)
        self.assertIn("Only aggregated numbers", out)
        self.assertIn("No code, prompts, or file paths", out)

    def test_contains_models(self):
        out = format_dry_run(self.payload)
        self.assertIn("opus-4-6", out)

    def test_contains_separator(self):
        out = format_dry_run(self.payload)
        self.assertIn("\u2500" * 10, out)


class TestReceipts(unittest.TestCase):
    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        self._orig = __import__("myburn.push", fromlist=["RECEIPTS_DIR"]).RECEIPTS_DIR

    def tearDown(self):
        import myburn.push
        myburn.push.RECEIPTS_DIR = self._orig
        import shutil
        shutil.rmtree(self._tmpdir, ignore_errors=True)

    def test_save_receipt_creates_file(self):
        import myburn.push
        myburn.push.RECEIPTS_DIR = Path(self._tmpdir) / "receipts"

        payload = {"month": "2026-02", "cost": 135.0, "version": "1.0"}
        path = save_receipt(payload)

        self.assertTrue(path.exists())
        data = json.loads(path.read_text())
        self.assertEqual(data["month"], "2026-02")
        self.assertEqual(data["cost"], 135.0)

    def test_is_first_push_empty(self):
        import myburn.push
        myburn.push.RECEIPTS_DIR = Path(self._tmpdir) / "receipts_empty"
        self.assertTrue(is_first_push())

    def test_is_first_push_after_save(self):
        import myburn.push
        myburn.push.RECEIPTS_DIR = Path(self._tmpdir) / "receipts_has"

        payload = {"month": "2026-02", "cost": 100.0}
        save_receipt(payload)
        self.assertFalse(is_first_push())


class TestMarkReceiptSynced(unittest.TestCase):
    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        self._orig = __import__("myburn.push", fromlist=["RECEIPTS_DIR"]).RECEIPTS_DIR
        import myburn.push
        myburn.push.RECEIPTS_DIR = Path(self._tmpdir) / "receipts"

    def tearDown(self):
        import myburn.push
        myburn.push.RECEIPTS_DIR = self._orig
        import shutil
        shutil.rmtree(self._tmpdir, ignore_errors=True)

    def test_mark_receipt_synced(self):
        """Verify _synced and _synced_at are added to existing receipt."""
        payload = {"month": "2026-02", "cost": 135.0, "version": "1.0"}
        save_receipt(payload)
        mark_receipt_synced("2026-02")

        import myburn.push
        path = myburn.push.RECEIPTS_DIR / "2026-02.json"
        data = json.loads(path.read_text())
        self.assertTrue(data["_synced"])
        self.assertIn("_synced_at", data)
        # Original fields preserved
        self.assertEqual(data["cost"], 135.0)

    def test_mark_receipt_synced_missing(self):
        """Marking a non-existent receipt should be a no-op."""
        mark_receipt_synced("1999-01")  # should not raise


class TestListReceipts(unittest.TestCase):
    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        self._orig = __import__("myburn.push", fromlist=["RECEIPTS_DIR"]).RECEIPTS_DIR
        import myburn.push
        myburn.push.RECEIPTS_DIR = Path(self._tmpdir) / "receipts"

    def tearDown(self):
        import myburn.push
        myburn.push.RECEIPTS_DIR = self._orig
        import shutil
        shutil.rmtree(self._tmpdir, ignore_errors=True)

    def test_list_receipts(self):
        """Verify receipt summaries are correct."""
        save_receipt({"month": "2026-01", "cost": 50.0, "generatedAt": "2026-01-31T00:00:00Z"})
        save_receipt({"month": "2026-02", "cost": 135.0, "generatedAt": "2026-02-25T00:00:00Z"})
        mark_receipt_synced("2026-02")

        receipts = list_receipts()
        self.assertEqual(len(receipts), 2)
        # Sorted descending by filename
        self.assertEqual(receipts[0]["month"], "2026-02")
        self.assertTrue(receipts[0]["synced"])
        self.assertEqual(receipts[1]["month"], "2026-01")
        self.assertFalse(receipts[1]["synced"])
        self.assertAlmostEqual(receipts[1]["cost"], 50.0)

    def test_list_receipts_empty(self):
        """No receipts dir → empty list."""
        receipts = list_receipts()
        self.assertEqual(receipts, [])


class TestRunPush(unittest.TestCase):
    """Integration tests for the run_push CLI flow."""

    def test_dry_run_no_receipt(self):
        """--dry-run should NOT create a receipt file."""
        import myburn.push
        tmpreceipts = Path(tempfile.mkdtemp()) / "receipts"
        myburn.push.RECEIPTS_DIR = tmpreceipts

        from myburn.push import run_push
        # Should not raise, should not create receipt
        run_push(dry_run=True, month="2026-02")
        self.assertFalse(tmpreceipts.exists() and any(tmpreceipts.glob("*.json")))

    def test_yes_saves_receipt(self):
        """--yes on second push should save without prompting."""
        import myburn.push
        tmpreceipts = Path(tempfile.mkdtemp()) / "receipts"
        myburn.push.RECEIPTS_DIR = tmpreceipts

        # Create a fake first receipt so it's not "first push"
        tmpreceipts.mkdir(parents=True)
        (tmpreceipts / "2026-01.json").write_text("{}")

        from myburn.push import run_push
        run_push(dry_run=False, month="2026-02", yes=True)
        self.assertTrue((tmpreceipts / "2026-02.json").exists())


class TestCLIParsing(unittest.TestCase):
    """Test argparse subcommand routing."""

    def test_push_subcommand(self):
        import argparse
        from myburn.cli import main
        # Patch sys.argv and verify it routes to push
        with patch("sys.argv", ["myburn", "push", "--dry-run", "--month", "02"]):
            with patch("myburn.cli._cmd_push") as mock_push:
                main()
                mock_push.assert_called_once()
                args = mock_push.call_args[0][0]
                self.assertTrue(args.dry_run)
                self.assertEqual(args.month, "02")

    def test_no_command_routes_to_dashboard(self):
        with patch("sys.argv", ["myburn", "--port", "9999"]):
            with patch("myburn.cli._cmd_dashboard") as mock_dash:
                from myburn.cli import main
                main()
                mock_dash.assert_called_once()
                args = mock_dash.call_args[0][0]
                self.assertEqual(args.port, 9999)

    def test_push_yes_flag(self):
        with patch("sys.argv", ["myburn", "push", "--yes"]):
            with patch("myburn.cli._cmd_push") as mock_push:
                from myburn.cli import main
                main()
                args = mock_push.call_args[0][0]
                self.assertTrue(args.yes)


class TestVerify(unittest.TestCase):
    """Tests for verify helpers: _load_full_receipts and _verify_local_chain."""

    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        self._orig = __import__("myburn.push", fromlist=["RECEIPTS_DIR"]).RECEIPTS_DIR
        import myburn.push
        myburn.push.RECEIPTS_DIR = Path(self._tmpdir) / "receipts"

    def tearDown(self):
        import myburn.push
        myburn.push.RECEIPTS_DIR = self._orig
        import shutil
        shutil.rmtree(self._tmpdir, ignore_errors=True)

    def test_load_full_receipts(self):
        """Should load receipts and skip versioned backups."""
        save_receipt({"month": "2026-01", "cost": 50.0, "version": "1.0"})
        save_receipt({"month": "2026-02", "cost": 100.0, "version": "1.0"})
        receipts = _load_full_receipts()
        self.assertEqual(set(receipts.keys()), {"2026-01", "2026-02"})
        self.assertAlmostEqual(receipts["2026-01"]["cost"], 50.0)

    def test_load_full_receipts_empty(self):
        """No receipts dir → empty dict."""
        receipts = _load_full_receipts()
        self.assertEqual(receipts, {})

    def test_verify_local_chain_valid(self):
        """Receipts with correct hashes should all pass."""
        save_receipt({"month": "2026-01", "cost": 50.0, "version": "1.0"})
        save_receipt({"month": "2026-02", "cost": 100.0, "version": "1.0"})
        receipts = _load_full_receipts()
        issues = _verify_local_chain(receipts)
        self.assertEqual(len(issues), 2)
        for line in issues:
            self.assertIn("\u2713", line)

    def test_verify_local_chain_tampered(self):
        """Tampered receipt hash should be detected."""
        save_receipt({"month": "2026-01", "cost": 50.0, "version": "1.0"})
        receipts = _load_full_receipts()
        # Tamper the hash
        receipts["2026-01"]["_hash"] = "sha256:tampered"
        # Write back
        import myburn.push
        path = myburn.push.RECEIPTS_DIR / "2026-01.json"
        path.write_text(json.dumps(receipts["2026-01"], indent=2))
        # Reload and verify
        receipts = _load_full_receipts()
        issues = _verify_local_chain(receipts)
        self.assertTrue(any("\u2717" in line for line in issues))


class TestAutoPush(unittest.TestCase):
    """Tests for check_auto_push() logic."""

    def test_auto_intervals_defined(self):
        """All modes should have interval values."""
        self.assertIn("daily", _AUTO_INTERVALS)
        self.assertIn("weekly", _AUTO_INTERVALS)
        self.assertIn("monthly", _AUTO_INTERVALS)

    @patch("myburn.auth.load_config", return_value={"auto_push": "off", "token": "tok"})
    def test_off_returns_false(self, mock_cfg):
        self.assertFalse(check_auto_push())

    @patch("myburn.auth.get_token", return_value=None)
    @patch("myburn.auth.load_config", return_value={"auto_push": "daily"})
    def test_no_token_returns_false(self, mock_cfg, mock_tok):
        self.assertFalse(check_auto_push())

    @patch("myburn.push._cloud_push", return_value={"ok": True})
    @patch("myburn.auth.save_config")
    @patch("myburn.auth.get_token", return_value="test-token")
    @patch("myburn.auth.load_config", return_value={"auto_push": "daily", "token": "test-token"})
    def test_daily_pushes_when_due(self, mock_cfg, mock_tok, mock_save, mock_cloud):
        """With no last_auto_push_at, should push immediately."""
        # Ensure DB has data (idempotent — skip if already seeded)
        conn = _get_db()
        count = conn.execute("SELECT COUNT(*) FROM daily_usage").fetchone()[0]
        conn.close()
        if count == 0:
            _seed_db()
        import myburn.push
        tmpreceipts = Path(tempfile.mkdtemp()) / "receipts"
        orig = myburn.push.RECEIPTS_DIR
        myburn.push.RECEIPTS_DIR = tmpreceipts
        try:
            result = check_auto_push()
            self.assertTrue(result)
            mock_cloud.assert_called_once()
            mock_save.assert_called_once()
        finally:
            myburn.push.RECEIPTS_DIR = orig


class TestCLIVerifyParsing(unittest.TestCase):
    """Test that 'verify' subcommand routes correctly."""

    def test_verify_subcommand(self):
        with patch("sys.argv", ["myburn", "verify"]):
            with patch("myburn.cli._cmd_verify") as mock_verify:
                from myburn.cli import main
                main()
                mock_verify.assert_called_once()


if __name__ == "__main__":
    unittest.main()
