from __future__ import annotations

import datetime as dt
from types import SimpleNamespace

import pytest

from app.models.scan import ScanJobStatus, ScanRequest, ScanResult
from app.services import scanner_jobs
from app.services.scanner import ScannerError


class _FakeDocRef:
    def __init__(self, store, doc_id):
        self._store = store
        self.id = doc_id

    def set(self, payload):
        self._store[self.id] = payload

    def get(self):
        data = self._store.get(self.id)
        exists = data is not None
        return SimpleNamespace(exists=exists, to_dict=lambda: data)

    def update(self, payload):
        if self.id not in self._store:
            raise KeyError(self.id)
        self._store[self.id].update(payload)


class _FakeCollection:
    def __init__(self, store):
        self._store = store

    def document(self, doc_id=None):
        doc_id = doc_id or _rand_id()
        if doc_id not in self._store:
            self._store[doc_id] = {}
        return _FakeDocRef(self._store, doc_id)

    def add(self, payload):
        doc_id = _rand_id()
        self._store[doc_id] = payload
        return _FakeDocRef(self._store, doc_id)


class _FakeFirestore:
    def __init__(self):
        self._collections = {}

    def collection(self, name):
        return self._collections.setdefault(name, _FakeCollection({}))


_id_counter = 0


def _rand_id():
    global _id_counter
    _id_counter += 1
    return f"id{_id_counter:04d}"


@pytest.fixture(autouse=True)
def _patch_firestore(monkeypatch):
    fake_db = _FakeFirestore()
    monkeypatch.setattr("app.services.scanner_jobs.get_firestore_client", lambda: fake_db)
    return fake_db


def test_enqueue_and_process_scan_job(monkeypatch):
    request = ScanRequest(prompt="hello", categories=["harmfulness"], user_id="user-1")

    fake_result = ScanResult(issues=[], firewall_results=[], credits_used=50)
    monkeypatch.setattr(
        "app.services.scanner_jobs.execute_scan",
        lambda req, user_id: fake_result,
    )

    job = scanner_jobs.enqueue_scan(request, user_id="user-1")
    assert job.status == ScanJobStatus.QUEUED

    # Process the job
    processed = scanner_jobs.process_scan_job(job.job_id)
    assert processed == fake_result

    stored = scanner_jobs.get_job(job.job_id, user_id="user-1")
    assert stored.status == ScanJobStatus.COMPLETED
    assert stored.result is not None
    assert stored.result.credits_used == 50


def test_scan_job_failure_updates_status(monkeypatch):
    request = ScanRequest(prompt="hello", categories=["harmfulness"], user_id="user-2")

    def _raise(*args, **kwargs):
        raise ScannerError("Prompt is required")

    monkeypatch.setattr("app.services.scanner_jobs.execute_scan", _raise)

    job = scanner_jobs.enqueue_scan(request, user_id="user-2")
    scanner_jobs.process_scan_job(job.job_id)

    stored = scanner_jobs.get_job(job.job_id, user_id="user-2")
    assert stored.status == ScanJobStatus.FAILED
    assert stored.error_message is not None


def test_scan_job_access_control(monkeypatch):
    request = ScanRequest(prompt="secure", categories=["jailbreak"], user_id="owner")
    monkeypatch.setattr(
        "app.services.scanner_jobs.execute_scan",
        lambda req, user_id: ScanResult(issues=[], firewall_results=[], credits_used=0),
    )

    job = scanner_jobs.enqueue_scan(request, user_id="owner")
    scanner_jobs.process_scan_job(job.job_id)

    with pytest.raises(scanner_jobs.ScannerError):
        scanner_jobs.get_job(job.job_id, user_id="intruder")
