import asyncio
import os
import sqlite3
import time
from pathlib import Path

import pytest

from clarabot_nano.server.fstx.tmpfiledb import TemporaryFileDatabase
from clarabot_nano.utility import parse_environment_flag
from clarabot_nano.utility.file import temporary_file_by_target
from clarabot_nano.utility.path.validate import normalize_absolute_path
from clarabot_nano_tests.testing.core.instance import ConfigTestingNanoInstanceHandler
from clarabot_nano_tests.testing.file import temp_path


@pytest.mark.asyncio
async def test_server_temp_file_db(tmpdir):
    async with TemporaryFileDatabase.create(asyncio.get_event_loop(), str(temp_path(tmpdir)), 10.0) as db:
        db: TemporaryFileDatabase
        assert await db.get_old_tmp_files(50, 10) == []
        assert await db.get_tmp_file(50, "") is None
        root = Path(normalize_absolute_path(temp_path(tmpdir)))
        root.mkdir()

        def new_row(a, n):
            f0 = root / f"file me Őp_{n}.doc"
            t0 = temporary_file_by_target(f0)
            r = (a, str(f0), t0, os.urandom(8))
            return r

        r0 = new_row(50, "x")
        r0 = (await db.create_tmp_file(*r0),) + r0

        assert await db.get_tmp_file(*r0[1:3]) == r0[:1] + r0[3:]
        assert await db.get_all_part_mac(r0[0]) == []
        await db.add_part_mac(r0[0], 1, 1, b"c1")
        assert await db.get_all_part_mac(r0[0]) == [(1, 1, b"c1")]
        await db.add_part_mac(r0[0], 2, 1, b"c2")
        assert set(await db.get_all_part_mac(r0[0])) == {(1, 1, b"c1"), (2, 1, b"c2")}
        await db.add_part_mac(r0[0], 1, 1, b"c1b")
        assert set(await db.get_all_part_mac(r0[0])) == {(1, 1, b"c1b"), (2, 1, b"c2")}
        assert await db.get_old_tmp_files(50, 10) == []
        olds = await db.get_old_tmp_files(0, 10)
        assert len(olds) == 1
        assert olds[0][:5] == r0

        r0_id2 = await db.create_tmp_file(*r0[1:])
        await db.add_part_mac(r0_id2, 3, 2, b"c3")
        olds = await db.get_old_tmp_files(0, 10)
        assert len(olds) == 1
        assert olds[0][:5] == (r0_id2,) + r0[1:5]

        assert r0[0] != r0_id2
        assert await db.get_all_part_mac(r0[0]) == []
        assert await db.get_all_part_mac(r0_id2) == [(3, 2, b"c3")]

        assert await db.get_tmp_file(50, "") is None
        await db.drop_tmp_file(r0[0])
        assert await db.get_all_part_mac(r0[0]) == []
        assert await db.get_all_part_mac(r0_id2) == [(3, 2, b"c3")]
        await db.drop_tmp_file(r0_id2)
        assert await db.get_all_part_mac(r0_id2) == []

        # pk uniqueness
        r0 = (await db.create_tmp_file(*r0[1:]),) + r0[1:]
        await db.add_part_mac(r0[0], 10, 1, b"0x")
        r1 = (await db.create_tmp_file(*(r := new_row(60, "x"))),) + r
        await db.add_part_mac(r1[0], 11, 1, b"1x")
        r2 = (await db.create_tmp_file(*(r := new_row(60, "y"))),) + r
        await db.add_part_mac(r2[0], 12, 1, b"1y")
        r3 = (await db.create_tmp_file(*(r := new_row(70, "y"))),) + r
        await db.add_part_mac(r3[0], 13, 1, b"2y")
        assert set(await db.get_all_part_mac(r0[0])) == {(10, 1, b"0x")}
        assert set(await db.get_all_part_mac(r1[0])) == {(11, 1, b"1x")}
        assert set(await db.get_all_part_mac(r2[0])) == {(12, 1, b"1y")}
        assert set(await db.get_all_part_mac(r3[0])) == {(13, 1, b"2y")}

        await db.drop_tmp_file(r2[0])
        assert set(await db.get_all_part_mac(r0[0])) == {(10, 1, b"0x")}
        assert set(await db.get_all_part_mac(r1[0])) == {(11, 1, b"1x")}
        assert await db.get_all_part_mac(r2[0]) == []
        assert set(await db.get_all_part_mac(r3[0])) == {(13, 1, b"2y")}

        await db.add_part_mac(r1[0], 9, 2, b"r")
        assert set(await db.get_all_part_mac(r1[0])) == {(9, 2, b"r"), (11, 1, b"1x")}

        # error foreign key
        with pytest.raises(sqlite3.IntegrityError):
            await db.add_part_mac(r2[0], 5, 1, b"x")


@pytest.mark.skipif(parse_environment_flag("CBNANO_TEST_WITHOUT_SERVER"), reason="server-dependant tests disabled")
@pytest.mark.asyncio
async def test_server_fstx_cleanup(tmpdir, monkeypatch, testing_server_manager, reset_user_app_dirs, dev_nano_root_dir):
    root_dir = Path(str(temp_path(tmpdir)))
    root_dir.mkdir()
    time_time = time.time
    delta = 0

    def _time():
        x = time_time() + delta
        print("TT_", x)
        return x

    with monkeypatch.context() as m:
        m.setattr(time, "time", _time)
        async with TemporaryFileDatabase.create(
            asyncio.get_event_loop(), str(Path(reset_user_app_dirs[1], "fstx.db")), 10.0
        ) as db:
            i = 0

            async def _mk_tmp():
                nonlocal i
                tmp_path = root_dir / f".#tmp{i}"
                target_path = (root_dir / f"akármi{i}").as_posix()
                await db.create_tmp_file(0, target_path, tmp_path.name, b"")
                i += 1
                return target_path, tmp_path

            tp0, tmp0 = await _mk_tmp()
            tmp0.write_bytes(b"asd0")

            delta = -30 * 60
            tp1, tmp1 = await _mk_tmp()
            tmp1.write_bytes(b"asd1")
            tp2, tmp2 = await _mk_tmp()

            delta = -21 * 60 * 60
            tp3, tmp3 = await _mk_tmp()
            tmp3.write_bytes(b"asd1")
            tp4, tmp4 = await _mk_tmp()

    async with TemporaryFileDatabase.create(
        asyncio.get_event_loop(), str(Path(reset_user_app_dirs[1], "fstx.db")), 10.0
    ) as db:
        assert await db.get_tmp_file(0, tp0, False) is not None
        assert await db.get_tmp_file(0, tp1, False) is not None
        assert await db.get_tmp_file(0, tp2, False) is not None
        assert await db.get_tmp_file(0, tp3, False) is not None
        assert await db.get_tmp_file(0, tp4, False) is not None
        assert len(await db.get_old_tmp_files(20 * 60, 10)) == 4

    async with ConfigTestingNanoInstanceHandler(
        reset_user_app_dirs,
        dev_nano_root_dir,
        testing_server_manager,
    ).run() as (pm, debug_info, session, dbg_config):
        for _ in range(20):
            if not tmp1.exists() and not tmp3.exists():
                break
            await asyncio.sleep(0.2)
        await asyncio.sleep(1.0)

    async with TemporaryFileDatabase.create(
        asyncio.get_event_loop(), str(Path(reset_user_app_dirs[1], "fstx.db")), 10.0
    ) as db:
        assert await db.get_tmp_file(0, tp0, False) is not None
        assert await db.get_tmp_file(0, tp1, False) is None
        assert await db.get_tmp_file(0, tp2, False) is not None
        assert await db.get_tmp_file(0, tp3, False) is None
        assert await db.get_tmp_file(0, tp4, False) is None
        assert len(await db.get_old_tmp_files(20 * 60, 10)) == 1
