from __future__ import annotations

import json
import os
import sqlite3
import time
from dataclasses import dataclass
from typing import Any, Optional


@dataclass
class CacheEntry:
    response_json: Any
    status_code: int
    created_at: int


class Cache:
    def __init__(self, path: str, ttl_days: int = 30, error_ttl_days: int = 1) -> None:
        self.path = os.path.expanduser(path)
        self.ttl_seconds = ttl_days * 86400
        self.error_ttl_seconds = error_ttl_days * 86400
        os.makedirs(os.path.dirname(self.path), exist_ok=True)
        self._init_db()

    def _init_db(self) -> None:
        with sqlite3.connect(self.path) as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS cache (
                    provider TEXT NOT NULL,
                    request_key TEXT NOT NULL,
                    response_json TEXT NOT NULL,
                    status_code INTEGER NOT NULL,
                    created_at INTEGER NOT NULL,
                    PRIMARY KEY (provider, request_key)
                )
                """
            )

    def get(self, provider: str, request_key: str) -> Optional[CacheEntry]:
        now = int(time.time())
        with sqlite3.connect(self.path) as conn:
            row = conn.execute(
                "SELECT response_json, status_code, created_at FROM cache WHERE provider=? AND request_key=?",
                (provider, request_key),
            ).fetchone()
        if not row:
            return None
        response_json, status_code, created_at = row
        ttl = self.error_ttl_seconds if status_code >= 400 else self.ttl_seconds
        if now - created_at > ttl:
            return None
        return CacheEntry(json.loads(response_json), int(status_code), int(created_at))

    def set(self, provider: str, request_key: str, response_json: Any, status_code: int) -> None:
        payload = json.dumps(response_json)
        with sqlite3.connect(self.path) as conn:
            conn.execute(
                "INSERT OR REPLACE INTO cache (provider, request_key, response_json, status_code, created_at) VALUES (?, ?, ?, ?, ?)",
                (provider, request_key, payload, int(status_code), int(time.time())),
            )


class NullCache:
    def get(self, provider: str, request_key: str) -> Optional[CacheEntry]:  # pragma: no cover - trivial
        return None

    def set(self, provider: str, request_key: str, response_json: Any, status_code: int) -> None:  # pragma: no cover
        return None
