from pytest import raises

from os import path
from os import mkdir

from tests.test_backup import backups
from tests.common import generate_backup_query


import selfprivacy_api.services as all_services
from selfprivacy_api.services import ServiceManager
from selfprivacy_api.graphql.common_types.service import service_to_graphql_service
from selfprivacy_api.graphql.common_types.backup import (
    _AutobackupQuotas,
    AutobackupQuotas,
)
from selfprivacy_api.jobs import Jobs, JobStatus
from selfprivacy_api.backup.storage import Storage
from selfprivacy_api.backup.local_secret import LocalBackupSecret

from tests.test_graphql.test_services import (
    # TODO: shuffle them to conftest
    only_dummy_service_and_api,
    only_dummy_service,
    dkim_file,
)
from selfprivacy_api.services import CONFIG_STASH_DIR
from tests.test_graphql.common import assert_empty


API_RELOAD_SNAPSHOTS = """
mutation TestSnapshotsReload {
    backup {
        forceSnapshotsReload {
            success
            message
            code
        }
    }
}
"""

API_TOTAL_BACKUP = """
mutation TestForcedAutobackup {
    backup {
         totalBackup{
            success
            message
            code
            job{
                uid
            }
        }
    }
}
"""

API_TOTAL_RESTORE = """
mutation TestTotalRestore {
    backup {
           restoreAll{
            success
            message
            code
			job{
			    uid
			}
        }
    }
}
"""

API_SET_AUTOBACKUP_PERIOD_MUTATION = """
mutation TestAutobackupPeriod($period: Int) {
    backup {
        setAutobackupPeriod(period: $period) {
            success
            message
            code
            configuration {
                provider
                encryptionKey
                isInitialized
                autobackupPeriod
                locationName
                locationId
            }
        }
    }
}
"""


API_SET_AUTOBACKUP_QUOTAS_MUTATION = """
mutation TestAutobackupQuotas($input: AutobackupQuotasInput!) {
    backup {
        setAutobackupQuotas(quotas: $input) {
            success
            message
            code
            configuration {
                provider
                encryptionKey
                isInitialized
                autobackupPeriod
                locationName
                locationId
                autobackupQuotas {
                    last
                    daily
                    weekly
                    monthly
                    yearly
                }
            }
        }
    }
}
"""

API_REMOVE_REPOSITORY_MUTATION = """
mutation TestRemoveRepo {
    backup {
        removeRepository {
            success
            message
            code
            configuration {
                provider
                encryptionKey
                isInitialized
                autobackupPeriod
                locationName
                locationId
            }
        }
    }
}
"""

API_INIT_MUTATION = """
mutation TestInitRepo($input: InitializeRepositoryInput!) {
    backup {
        initializeRepository(repository: $input) {
            success
            message
            code
            configuration {
                provider
                encryptionKey
                isInitialized
                autobackupPeriod
                locationName
                locationId
            }
        }
    }
}
"""

API_RESTORE_MUTATION = """
mutation TestRestoreService($snapshot_id: String!) {
    backup {
        restoreBackup(snapshotId: $snapshot_id) {
            success
            message
            code
            job {
                uid
                status
            }
        }
    }
}
"""

API_FORGET_MUTATION = """
mutation TestForgetSnapshot($snapshot_id: String!) {
    backup {
        forgetSnapshot(snapshotId: $snapshot_id) {
            success
            message
            code
        }
    }
}
"""

API_LAST_SLICE_QUERY = """
lastSlice {
    id
    service {
        id
        displayName
    }
    createdAt
    reason
}
"""

API_SNAPSHOTS_QUERY = """
allSnapshots {
    id
    service {
        id
        displayName
    }
    createdAt
    reason
}
"""

API_BACKUP_SETTINGS_QUERY = """
configuration {
        provider
        encryptionKey
        isInitialized
        autobackupPeriod
        locationName
        locationId
    }
"""

API_BACK_UP_MUTATION = """
mutation TestBackupService($service_id: String!) {
    backup {
        startBackup(serviceId: $service_id) {
            success
            message
            code
            job {
                uid
                status
            }
        }
    }
}
"""


def api_restore(authorized_client, snapshot_id):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_RESTORE_MUTATION,
            "variables": {"snapshot_id": snapshot_id},
        },
    )
    return response


def api_backup(authorized_client, service):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_BACK_UP_MUTATION,
            "variables": {"service_id": service.get_id()},
        },
    )
    return response


def api_forget(authorized_client, snapshot_id):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_FORGET_MUTATION,
            "variables": {"snapshot_id": snapshot_id},
        },
    )
    return response


def api_set_period(authorized_client, period):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_SET_AUTOBACKUP_PERIOD_MUTATION,
            "variables": {"period": period},
        },
    )
    return response


def api_set_quotas(authorized_client, quotas: _AutobackupQuotas):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_SET_AUTOBACKUP_QUOTAS_MUTATION,
            "variables": {"input": quotas.model_dump()},
        },
    )
    return response


def api_remove(authorized_client):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_REMOVE_REPOSITORY_MUTATION,
            "variables": {},
        },
    )
    return response


def api_reload_snapshots(authorized_client):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_RELOAD_SNAPSHOTS,
            "variables": {},
        },
    )
    return response


def api_total_backup(authorized_client):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_TOTAL_BACKUP,
            "variables": {},
        },
    )
    return response


def api_restore_all(authorized_client):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_TOTAL_RESTORE,
            "variables": {},
        },
    )
    return response


def api_init(
    authorized_client,
    kind,
    login,
    password,
    location_name,
    location_id,
    local_secret=None,
):
    response = authorized_client.post(
        "/graphql",
        json={
            "query": API_INIT_MUTATION,
            "variables": {
                "input": {
                    "provider": kind,
                    "locationId": location_id,
                    "locationName": location_name,
                    "login": login,
                    "password": password,
                    "localSecret": local_secret,
                }
            },
        },
    )
    return response


def assert_ok(data):
    if data["success"] is False:
        # convenience for debugging, this should display error
        # if empty, consider adding helpful messages
        raise ValueError(data["code"], data["message"])
    assert data["code"] == 200
    assert data["success"] is True


def get_data(response):
    assert response.status_code == 200
    response = response.json()
    if (
        "errors" in response.keys()
    ):  # convenience for debugging, this will display error
        raise ValueError(response["errors"])
    assert response["data"] is not None
    data = response["data"]
    return data


def api_snapshots(authorized_client):
    response = authorized_client.post(
        "/graphql",
        json={"query": generate_backup_query([API_SNAPSHOTS_QUERY])},
    )
    data = get_data(response)
    result = data["backup"]["allSnapshots"]
    assert result is not None
    return result


def api_last_slice(authorized_client):
    response = authorized_client.post(
        "/graphql",
        json={"query": generate_backup_query([API_LAST_SLICE_QUERY])},
    )
    data = get_data(response)
    result = data["backup"]["lastSlice"]
    assert result is not None
    return result


def api_settings(authorized_client):
    response = authorized_client.post(
        "/graphql",
        json={"query": generate_backup_query([API_BACKUP_SETTINGS_QUERY])},
    )
    data = get_data(response)
    result = data["backup"]["configuration"]
    assert result is not None
    return result


def test_dummy_service_convertible_to_gql(dummy_service):
    gql_service = service_to_graphql_service(dummy_service)
    assert gql_service is not None


def test_snapshots_empty(authorized_client, dummy_service, backups):
    snaps = api_snapshots(authorized_client)
    assert snaps == []


def test_snapshots_orphaned_service(authorized_client, dummy_service, backups):
    api_backup(authorized_client, dummy_service)
    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 1

    all_services.services.remove(dummy_service)
    assert ServiceManager.get_service_by_id(dummy_service.get_id()) is None

    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 1
    assert "Orphaned" in snaps[0]["service"]["displayName"]
    assert dummy_service.get_id() in snaps[0]["service"]["displayName"]


def test_start_backup(authorized_client, dummy_service, backups):
    response = api_backup(authorized_client, dummy_service)
    data = get_data(response)["backup"]["startBackup"]
    assert data["success"] is True
    job = data["job"]

    assert Jobs.get_job(job["uid"]).status == JobStatus.FINISHED
    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 1
    snap = snaps[0]

    assert snap["id"] is not None
    assert snap["id"] != ""
    assert snap["service"]["id"] == "testservice"


def test_restore(authorized_client, dummy_service, backups):
    api_backup(authorized_client, dummy_service)
    snap = api_snapshots(authorized_client)[0]
    assert snap["id"] is not None

    response = api_restore(authorized_client, snap["id"])
    data = get_data(response)["backup"]["restoreBackup"]
    assert data["success"] is True
    job = data["job"]

    assert Jobs.get_job(job["uid"]).status == JobStatus.FINISHED


def test_reinit(authorized_client, dummy_service, tmpdir, backups):
    test_repo_path = path.join(tmpdir, "not_at_all_sus")
    response = api_init(authorized_client, "FILE", "", "", test_repo_path, "")
    data = get_data(response)["backup"]["initializeRepository"]
    assert_ok(data)
    configuration = data["configuration"]
    assert configuration["provider"] == "FILE"
    assert configuration["locationId"] == ""
    assert configuration["locationName"] == test_repo_path
    assert len(configuration["encryptionKey"]) > 1
    assert configuration["isInitialized"] is True

    response = api_backup(authorized_client, dummy_service)
    data = get_data(response)["backup"]["startBackup"]
    assert data["success"] is True
    job = data["job"]

    assert Jobs.get_job(job["uid"]).status == JobStatus.FINISHED


def test_migrate_backup_repo(authorized_client, dummy_service, tmpdir, backups):
    """
    Simulate the workflow of migrating to a new server
    """
    # Using an alternative path to be sure that we do not
    # match only by incident
    test_repo_path = path.join(tmpdir, "not_at_all_sus")
    response = api_init(authorized_client, "FILE", "", "", test_repo_path, "")
    data = get_data(response)["backup"]["initializeRepository"]
    assert_ok(data)
    snaps = api_snapshots(authorized_client)
    assert snaps == []

    # Now, forget what we just did
    del test_repo_path
    del response
    del data
    del snaps

    # I am a user at my old machine, I make a backup
    response = api_backup(authorized_client, dummy_service)
    data = get_data(response)["backup"]["startBackup"]
    assert_ok(data)

    # Then oh no, we need to migrate, we get our settings.
    # Because we have forgot everything 2000 times already
    # Was years, was years.
    # I still remember login though
    configuration = api_settings(authorized_client)

    # Ok. Let's now go to another machine
    # Another machine will not have any settings at all

    Storage.reset()
    LocalBackupSecret._full_reset()

    # That's it, nothing left
    new_configuration = api_settings(authorized_client)
    assert new_configuration["isInitialized"] is False

    # Reinit
    response = api_init(
        authorized_client,
        kind=configuration["provider"],
        login="",  # user provides login and password, configuration endpoint does not
        password="",  # empty for file based repository
        location_name=configuration["locationName"],
        location_id=configuration["locationId"],
        local_secret=configuration["encryptionKey"],
    )
    data = get_data(response)["backup"]["initializeRepository"]
    assert_ok(data)
    assert data["configuration"] == configuration

    new_configuration = api_settings(authorized_client)
    assert new_configuration == configuration

    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 1


def test_remove(authorized_client, generic_userdata, backups):
    response = api_remove(authorized_client)
    data = get_data(response)["backup"]["removeRepository"]
    assert_ok(data)

    configuration = data["configuration"]
    assert configuration["provider"] == "NONE"
    assert configuration["locationId"] == ""
    assert configuration["locationName"] == ""
    # still generated every time it is missing
    assert len(configuration["encryptionKey"]) > 1
    assert configuration["isInitialized"] is False


def test_autobackup_quotas_nonzero(authorized_client, backups):
    quotas = _AutobackupQuotas(
        last=3,
        daily=2,
        weekly=4,
        monthly=13,
        yearly=14,
    )
    response = api_set_quotas(authorized_client, quotas)
    data = get_data(response)["backup"]["setAutobackupQuotas"]
    assert_ok(data)

    configuration = data["configuration"]
    assert configuration["autobackupQuotas"] == quotas.model_dump()


def test_autobackup_period_nonzero(authorized_client, backups):
    new_period = 11
    response = api_set_period(authorized_client, new_period)
    data = get_data(response)["backup"]["setAutobackupPeriod"]
    assert_ok(data)

    configuration = data["configuration"]
    assert configuration["autobackupPeriod"] == new_period


def test_autobackup_period_zero(authorized_client, backups):
    new_period = 0
    # since it is none by default, we better first set it to something non-negative
    response = api_set_period(authorized_client, 11)
    # and now we nullify it
    response = api_set_period(authorized_client, new_period)
    data = get_data(response)["backup"]["setAutobackupPeriod"]
    assert_ok(data)

    configuration = data["configuration"]
    assert configuration["autobackupPeriod"] == None


def test_autobackup_period_none(authorized_client, backups):
    # since it is none by default, we better first set it to something non-negative
    response = api_set_period(authorized_client, 11)
    # and now we nullify it
    response = api_set_period(authorized_client, None)
    data = get_data(response)["backup"]["setAutobackupPeriod"]
    assert_ok(data)

    configuration = data["configuration"]
    assert configuration["autobackupPeriod"] == None


def test_autobackup_period_negative(authorized_client, backups):
    # since it is none by default, we better first set it to something non-negative
    response = api_set_period(authorized_client, 11)
    # and now we nullify it
    response = api_set_period(authorized_client, -12)
    data = get_data(response)["backup"]["setAutobackupPeriod"]
    assert_ok(data)

    configuration = data["configuration"]
    assert configuration["autobackupPeriod"] == None


# We cannot really check the effect at this level, we leave it to backend tests
# But we still make it run in both empty and full scenarios and ask for snaps afterwards
def test_reload_snapshots_bare_bare_bare(authorized_client, dummy_service, backups):
    api_remove(authorized_client)

    response = api_reload_snapshots(authorized_client)
    data = get_data(response)["backup"]["forceSnapshotsReload"]
    assert_ok(data)

    snaps = api_snapshots(authorized_client)
    assert snaps == []


def test_total_backup_if_dir_exists(
    authorized_client, only_dummy_service_and_api, backups
):
    # mkdir(CONFIG_STASH_DIR)
    dummy_service = only_dummy_service_and_api

    response = api_total_backup(authorized_client)
    # raise ValueError(get_data(response))
    data = get_data(response)["backup"]["totalBackup"]
    assert_ok(data)

    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 2


def test_total_backup(authorized_client, only_dummy_service_and_api, backups):
    dummy_service = only_dummy_service_and_api

    response = api_total_backup(authorized_client)
    # raise ValueError(get_data(response))
    data = get_data(response)["backup"]["totalBackup"]
    assert_ok(data)

    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 2


def test_reload_snapshots(authorized_client, dummy_service, backups):
    response = api_backup(authorized_client, dummy_service)
    data = get_data(response)["backup"]["startBackup"]

    response = api_reload_snapshots(authorized_client)
    data = get_data(response)["backup"]["forceSnapshotsReload"]
    assert_ok(data)

    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 1


def test_forget_snapshot(authorized_client, dummy_service, backups):
    response = api_backup(authorized_client, dummy_service)
    data = get_data(response)["backup"]["startBackup"]

    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 1

    response = api_forget(authorized_client, snaps[0]["id"])
    data = get_data(response)["backup"]["forgetSnapshot"]
    assert_ok(data)

    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 0


def test_forget_nonexistent_snapshot(authorized_client, dummy_service, backups):
    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 0
    response = api_forget(authorized_client, "898798uekiodpjoiweoiwuoeirueor")
    data = get_data(response)["backup"]["forgetSnapshot"]
    assert data["code"] == 404
    assert data["success"] is False

    snaps = api_snapshots(authorized_client)
    assert len(snaps) == 0


def test_last_slice(authorized_client, only_dummy_service_and_api, backups):
    api_total_backup(authorized_client)
    snaps = api_last_slice(authorized_client)

    assert len(snaps) == 2


def test_backup_all_restore_all(
    authorized_client,
    backups,
    generic_userdata,
    dkim_file,
    only_dummy_service_and_api,
    catch_nixos_rebuild_calls,
):
    dummy_service = only_dummy_service_and_api
    fp = catch_nixos_rebuild_calls
    fp.pass_command(["restic", fp.any()])
    fp.keep_last_process(True)
    fp.pass_command(["rclone", fp.any()])
    fp.keep_last_process(True)
    fp.pass_command(["lsblk", fp.any()])
    fp.keep_last_process(True)

    response = api_total_backup(authorized_client)
    data = get_data(response)["backup"]["totalBackup"]
    assert_ok(data)

    assert len(api_snapshots(authorized_client)) == 2
    response = api_restore_all(authorized_client)
    data = get_data(response)["backup"]["restoreAll"]
    assert_ok(data)
    # Just in case
    assert len(api_snapshots(authorized_client)) == 2


def test_unauthorized(client, dummy_service):
    quotas = _AutobackupQuotas(
        last=3,
        daily=2,
        weekly=4,
        monthly=13,
        yearly=14,
    )
    response = api_set_quotas(client, quotas)
    assert_empty(response)
    response = api_set_period(client, 11)
    assert_empty(response)
    response = api_remove(client)
    assert_empty(response)
    response = api_init(client, "FILE", "", "", "/boguspath", "")
    assert_empty(response)
    response = api_restore(client, "oiueroiwueo")
    assert_empty(response)
    response = api_backup(client, dummy_service)
    assert_empty(response)
    response = api_forget(client, "worieuoweiru")
    assert_empty(response)
    response = api_total_backup(client)
    assert_empty(response)
    response = api_restore_all(client)
    assert_empty(response)
    response = api_reload_snapshots(client)
    assert_empty(response)

    # TODO: make a special error for this in get_data
    with raises(ValueError):
        api_last_slice(client)
    with raises(ValueError):
        api_snapshots(client)