#!/usr/bin/env python3
"""
Lightweight HTTP server implementing a REST API for giveaway management.

Endpoints:
  POST   /api/giveaways                -> create a giveaway
  GET    /api/giveaways                -> list giveaways
  GET    /api/giveaways/<id>           -> details for a giveaway
  POST   /api/giveaways/<id>/enter     -> enter a giveaway
  POST   /api/giveaways/<id>/draw      -> draw winners
  GET    /api/giveaways/<id>/entrants  -> list entrants
  GET    /api/giveaways/<id>/winners   -> list winners
  GET    /health                       -> simple health check

Data is stored in a SQLite database whose path is determined by the environment
variable DB_PATH or defaults to `data.db` in the current directory.

This implementation uses only Python's standard library and is designed to be
easy to reason about.  It is not optimised for high throughput or concurrent
access, but suffices for demonstration and testing purposes.
"""

import json
import os
import sqlite3
import time
import secrets
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs
from uuid import uuid4


DB_PATH = os.environ.get('DB_PATH', os.path.join(os.path.dirname(__file__), 'data.db'))


def now() -> int:
    """Return current time in seconds since the epoch."""
    return int(time.time())


def init_db():
    """Initialise the SQLite database and ensure tables exist."""
    conn = sqlite3.connect(DB_PATH)
    conn.executescript(
        """
        CREATE TABLE IF NOT EXISTS giveaways (
            id TEXT PRIMARY KEY,
            title TEXT NOT NULL,
            description TEXT,
            prize TEXT NOT NULL,
            start_at INTEGER NOT NULL,
            end_at INTEGER NOT NULL,
            number_of_winners INTEGER NOT NULL,
            status TEXT NOT NULL,
            created_at INTEGER NOT NULL,
            updated_at INTEGER NOT NULL
        );
        CREATE TABLE IF NOT EXISTS entrants (
            id TEXT PRIMARY KEY,
            giveaway_id TEXT NOT NULL,
            email TEXT,
            username TEXT,
            ip TEXT,
            user_agent TEXT,
            created_at INTEGER NOT NULL,
            FOREIGN KEY(giveaway_id) REFERENCES giveaways(id)
        );
        CREATE TABLE IF NOT EXISTS winners (
            id TEXT PRIMARY KEY,
            giveaway_id TEXT NOT NULL,
            entrant_id TEXT NOT NULL,
            drawn_at INTEGER NOT NULL,
            FOREIGN KEY(giveaway_id) REFERENCES giveaways(id),
            FOREIGN KEY(entrant_id) REFERENCES entrants(id)
        );
        CREATE TABLE IF NOT EXISTS audit (
            id TEXT PRIMARY KEY,
            giveaway_id TEXT,
            action TEXT NOT NULL,
            payload TEXT,
            created_at INTEGER NOT NULL
        );
        """
    )
    conn.commit()
    conn.close()


def record_audit(gid: str | None, action: str, payload: dict):
    conn = sqlite3.connect(DB_PATH)
    conn.execute(
        "INSERT INTO audit (id, giveaway_id, action, payload, created_at) VALUES (?, ?, ?, ?, ?)",
        (str(uuid4()), gid, action, json.dumps(payload), now()),
    )
    conn.commit()
    conn.close()


class GiveawayRequestHandler(BaseHTTPRequestHandler):
    """HTTP request handler for the giveaway API."""

    server_version = "GiveawayServer/1.0"
    sys_version = ""

    def _set_headers(self, status: int = 200, content_type: str = "application/json"):
        self.send_response(status)
        self.send_header("Content-Type", content_type)
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
        self.send_header("Access-Control-Allow-Headers", "Content-Type")
        self.end_headers()

    def _read_json(self) -> dict:
        length = int(self.headers.get('Content-Length', 0))
        raw = self.rfile.read(length) if length > 0 else b''
        if not raw:
            return {}
        try:
            return json.loads(raw.decode('utf-8'))
        except json.JSONDecodeError:
            return {}

    def _write_json(self, data: dict, status: int = 200):
        body = json.dumps(data).encode('utf-8')
        self._set_headers(status)
        self.wfile.write(body)

    def do_OPTIONS(self):
        # Respond to preflight CORS requests
        self._set_headers()

    def do_GET(self):
        parsed = urlparse(self.path)
        path_parts = parsed.path.strip('/').split('/')
        if parsed.path == '/health':
            return self._write_json({"status": "ok", "timestamp": now()})

        if path_parts[:2] == ['api', 'giveaways']:
            # /api/giveaways or /api/giveaways/<id>/... endpoints
            if len(path_parts) == 2:
                return self._handle_list_giveaways()
            elif len(path_parts) >= 3:
                gid = path_parts[2]
                if len(path_parts) == 3:
                    return self._handle_get_giveaway(gid)
                elif len(path_parts) == 4:
                    sub = path_parts[3]
                    if sub == 'entrants':
                        return self._handle_list_entrants(gid)
                    elif sub == 'winners':
                        return self._handle_list_winners(gid)
        # Unknown path
        return self._write_json({"error": "Not Found"}, status=404)

    def do_POST(self):
        parsed = urlparse(self.path)
        path_parts = parsed.path.strip('/').split('/')
        if path_parts[:2] == ['api', 'giveaways']:
            if len(path_parts) == 2:
                return self._handle_create_giveaway()
            elif len(path_parts) >= 3:
                gid = path_parts[2]
                if len(path_parts) == 4:
                    sub = path_parts[3]
                    if sub == 'enter':
                        return self._handle_enter_giveaway(gid)
                    elif sub == 'draw':
                        return self._handle_draw_giveaway(gid)
        # Unknown path
        return self._write_json({"error": "Not Found"}, status=404)

    # --- Handlers ---

    def _handle_list_giveaways(self):
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        rows = conn.execute(
            "SELECT id, title, prize, start_at, end_at, number_of_winners, status FROM giveaways ORDER BY created_at DESC"
        ).fetchall()
        giveaways = [dict(row) for row in rows]
        conn.close()
        return self._write_json(giveaways)

    def _handle_get_giveaway(self, gid: str):
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        g = conn.execute("SELECT * FROM giveaways WHERE id = ?", (gid,)).fetchone()
        if not g:
            conn.close()
            return self._write_json({"error": "giveaway not found"}, status=404)
        # Count entrants and winners
        entrant_count = conn.execute(
            "SELECT COUNT(*) FROM entrants WHERE giveaway_id = ?", (gid,)
        ).fetchone()[0]
        winner_count = conn.execute(
            "SELECT COUNT(*) FROM winners WHERE giveaway_id = ?", (gid,)
        ).fetchone()[0]
        giveaway = dict(g)
        giveaway['entrantCount'] = entrant_count
        giveaway['winnerCount'] = winner_count
        conn.close()
        return self._write_json(giveaway)

    def _handle_list_entrants(self, gid: str):
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        exists = conn.execute("SELECT 1 FROM giveaways WHERE id = ?", (gid,)).fetchone()
        if not exists:
            conn.close()
            return self._write_json({"error": "giveaway not found"}, status=404)
        rows = conn.execute(
            "SELECT id, email, username, created_at FROM entrants WHERE giveaway_id = ? ORDER BY created_at ASC",
            (gid,),
        ).fetchall()
        entrants = [dict(row) for row in rows]
        conn.close()
        return self._write_json(entrants)

    def _handle_list_winners(self, gid: str):
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        exists = conn.execute("SELECT 1 FROM giveaways WHERE id = ?", (gid,)).fetchone()
        if not exists:
            conn.close()
            return self._write_json({"error": "giveaway not found"}, status=404)
        rows = conn.execute(
            "SELECT winners.id AS id, winners.entrant_id AS entrant_id, winners.drawn_at AS drawn_at, entrants.email, entrants.username "
            "FROM winners JOIN entrants ON winners.entrant_id = entrants.id WHERE winners.giveaway_id = ?",
            (gid,),
        ).fetchall()
        winners = [dict(row) for row in rows]
        conn.close()
        return self._write_json(winners)

    def _handle_create_giveaway(self):
        data = self._read_json()
        title = data.get('title')
        prize = data.get('prize')
        if not title or not prize:
            return self._write_json({"error": "title and prize are required"}, status=400)
        description = data.get('description', '')
        winners = int(data.get('number_of_winners', 1))
        start_at = int(data.get('start_at', now()))
        end_at = int(data.get('end_at', start_at + 86400))
        gid = str(uuid4())
        timestamp = now()
        conn = sqlite3.connect(DB_PATH)
        conn.execute(
            "INSERT INTO giveaways (id, title, description, prize, start_at, end_at, number_of_winners, status, created_at, updated_at) "
            "VALUES (?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)",
            (gid, title, description, prize, start_at, end_at, winners, timestamp, timestamp),
        )
        conn.commit()
        conn.close()
        record_audit(gid, 'created', {'title': title, 'prize': prize, 'start_at': start_at, 'end_at': end_at, 'winners': winners})
        return self._write_json({'id': gid}, status=201)

    def _handle_enter_giveaway(self, gid: str):
        data = self._read_json()
        email = data.get('email')
        username = data.get('username')
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        # Validate giveaway exists and is active
        g = conn.execute("SELECT * FROM giveaways WHERE id = ?", (gid,)).fetchone()
        if not g:
            conn.close()
            return self._write_json({"error": "giveaway not found"}, status=404)
        now_ts = now()
        if now_ts > g['end_at'] or g['status'] != 'active':
            conn.close()
            return self._write_json({"error": "giveaway is closed"}, status=400)
        # Check duplicate entry by email
        if email:
            existing = conn.execute(
                "SELECT id FROM entrants WHERE giveaway_id = ? AND email = ?",
                (gid, email),
            ).fetchone()
            if existing:
                conn.close()
                return self._write_json({"error": "email already entered"}, status=409)
        entrant_id = str(uuid4())
        ip = self.client_address[0]
        user_agent = self.headers.get('User-Agent', '')
        conn.execute(
            "INSERT INTO entrants (id, giveaway_id, email, username, ip, user_agent, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
            (entrant_id, gid, email, username, ip, user_agent, now_ts),
        )
        conn.commit()
        conn.close()
        record_audit(gid, 'entered', {'entrant_id': entrant_id, 'email': email, 'username': username, 'ip': ip})
        return self._write_json({'entrantId': entrant_id}, status=201)

    def _handle_draw_giveaway(self, gid: str):
        conn = sqlite3.connect(DB_PATH)
        conn.row_factory = sqlite3.Row
        g = conn.execute("SELECT * FROM giveaways WHERE id = ?", (gid,)).fetchone()
        if not g:
            conn.close()
            return self._write_json({"error": "giveaway not found"}, status=404)
        if g['status'] == 'drawn':
            conn.close()
            return self._write_json({"error": "winners already drawn"}, status=409)
        # Fetch entrants
        entrants = [row[0] for row in conn.execute("SELECT id FROM entrants WHERE giveaway_id = ?", (gid,)).fetchall()]
        total = len(entrants)
        if total == 0:
            conn.close()
            return self._write_json({"error": "no entrants"}, status=400)
        winners_needed = min(g['number_of_winners'], total)
        # Draw random winners without repetition
        winner_ids = set()
        while len(winner_ids) < winners_needed:
            idx = secrets.randbelow(total)
            winner_ids.add(entrants[idx])
        drawn_at = now()
        winners_arr = []
        for entrant_id in winner_ids:
            wid = str(uuid4())
            conn.execute(
                "INSERT INTO winners (id, giveaway_id, entrant_id, drawn_at) VALUES (?, ?, ?, ?)",
                (wid, gid, entrant_id, drawn_at),
            )
            winners_arr.append({'id': wid, 'entrant_id': entrant_id})
        conn.execute(
            "UPDATE giveaways SET status = 'drawn', updated_at = ? WHERE id = ?",
            (drawn_at, gid),
        )
        conn.commit()
        conn.close()
        nonce = secrets.token_hex(16)
        record_audit(gid, 'draw', {'winners': winners_arr, 'nonce': nonce, 'total': total, 'timestamp': drawn_at})
        return self._write_json({'winners': winners_arr, 'nonce': nonce})


def run_server(port: int = 8000):
    init_db()
    server = HTTPServer(('0.0.0.0', port), GiveawayRequestHandler)
    print(f"Random.com giveaway Python server listening on port {port}")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nShutting down server...")
    finally:
        server.server_close()


if __name__ == '__main__':
    run_server(int(os.environ.get('PORT', '8000')))