import os
import time
import random
import logging
import json
from os.path import join
import tornado.ioloop
import tornado.web
from tornado.websocket import WebSocketHandler

# Constants for paths
UPLOADS_DIR = "/var/www/html/prazskysvazjuda.cz/uploads"
METADATA_FILE = join(UPLOADS_DIR, "uploads_metadata.json")
IMAGES_DIR = join(UPLOADS_DIR, "images")

# Configure logging
LOG_FILE = "/var/log/tornado/tornado_uploads.log"
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler(LOG_FILE),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# Base handler for session management
class BaseHandler(tornado.web.RequestHandler):
    def get_current_user(self):
        user_id = self.get_secure_cookie("user_id")
        return user_id.decode() if user_id else None

    def set_current_user(self, user_id):
        self.set_secure_cookie("user_id", user_id)


# WebSocket handler to notify clients of updates
class GalleryWebSocketHandler(WebSocketHandler):
    connections = set()

    def open(self):
        self.connections.add(self)
        logger.info("New WebSocket connection")

    def on_close(self):
        self.connections.discard(self)
        logger.info("WebSocket connection closed")

    @classmethod
    def notify_all(cls):
        for conn in cls.connections:
            conn.write_message("refresh")


# Handler for image uploads
class UploadHandler(BaseHandler):
    def get(self):
        """
        Handles GET requests to render the upload page. Assigns a new user ID
        if the user does not already have one.
        """
        if not self.get_current_user():
            # Generate a random user ID and set it in a secure cookie
            user_id = str(random.randint(100000, 999999))
            self.set_current_user(user_id)
            logger.info("Generated new user ID: %s", user_id)
        
        # Render the upload HTML page
        self.render("/var/www/html/prazskysvazjuda.cz/dist/sections/upload.html")

    def post(self):
        """
        Handles POST requests for image uploads. Validates the request, saves
        the uploaded image, stores metadata, and redirects to the gallery page
        upon success.
        """
        logger.info("Handling image upload")
        user_id = self.get_current_user()
        if not user_id:
            logger.warning("No user session found")
            self.set_status(400)  # Bad Request
            self.write({"status": "error", "message": "No user session. Please try uploading again."})
            return

        # Ensure a file is included in the request
        if 'image' not in self.request.files:
            logger.error("No file uploaded in the request")
            self.set_status(400)  # Bad Request
            self.write({"status": "error", "message": "No file uploaded"})
            return

        fileinfo = self.request.files['image'][0]
        ext = fileinfo['filename'].split(".")[-1].lower()
        logger.info(f"Received file: {fileinfo['filename']} with extension: {ext}")

        # Validate the file type
        allowed_extensions = ['jpg', 'jpeg', 'webp', 'png', 'gif', 'heic', 'bmp', 'tiff']
        if ext not in allowed_extensions:
            logger.error(f"Unsupported file format: {ext}")
            self.set_status(415)  # Unsupported Media Type
            self.write({"status": "error", "message": "Unsupported file format"})
            return

        # Generate a unique filename and save the file
        unique_fname = f'image-{int(time.time())}-{random.randint(100000000, 999999999)}.{ext}'
        save_path = join(IMAGES_DIR, unique_fname)
        logger.info(f"Saving file to: {save_path}")

        try:
            # Write the file to the specified path
            with open(save_path, 'wb') as f:
                f.write(fileinfo['body'])
            
            # Save the file metadata
            self.save_metadata(unique_fname, user_id)
            
            # Log success and redirect to the gallery page
            logger.info(f"File saved successfully: {save_path}")
            self.redirect("/sections/gallery.html")
        except Exception as e:
            # Handle any exceptions during file saving
            logger.exception(f"Error saving file {save_path}: {e}")
            self.set_status(500)  # Internal Server Error
            self.write({"status": "error", "message": f"Error saving file: {e}"})

    def save_metadata(self, filename, user_id):
        """
        Saves metadata for the uploaded file, including the filename, user ID,
        and timestamp.
        """
        metadata = {
            "filename": filename,
            "user_id": user_id,
            "timestamp": time.strftime("%Y-%m-%d %H:%M:%S")
        }
        logger.info(f"Saving metadata: {metadata}")
        try:
            # Load existing metadata or create a new list if the file doesn't exist
            try:
                with open(METADATA_FILE, 'r') as f:
                    data = json.load(f)
            except (FileNotFoundError, json.JSONDecodeError):
                data = []
            
            # Add the new metadata entry and save back to the file
            data.append(metadata)
            with open(METADATA_FILE, 'w') as f:
                json.dump(data, f, indent=4)
            
            logger.info("Metadata saved successfully")
        except Exception as e:
            # Handle exceptions during metadata saving
            logger.exception(f"Error saving metadata: {e}")


# API to list uploaded images
class ImageListAPI(tornado.web.RequestHandler):
    def get(self):
        try:
            with open(METADATA_FILE, 'r') as f:
                metadata = json.load(f)
            self.write({"files": sorted(metadata, key=lambda x: x["timestamp"], reverse=True)})
        except FileNotFoundError:
            self.write({"files": []})  # No metadata file
        except Exception as e:
            logger.error(f"Error reading metadata: {e}")
            self.set_status(500)
            self.write({"error": str(e)})


# API to delete uploaded images
class DeleteImageAPI(BaseHandler):
    def post(self, filename):
        user_id = self.get_current_user()
        if not user_id:
            self.set_status(403)
            self.write({"status": "error", "message": "Unauthorized access."})
            return

        file_path = join(IMAGES_DIR, filename)
        if not os.path.exists(file_path):
            self.set_status(404)
            self.write({"status": "error", "message": f"File {filename} not found."})
            return

        if self.can_user_delete(filename, user_id):
            try:
                os.remove(file_path)
                self.remove_metadata(filename)
                self.write({"status": "success", "message": f"File {filename} deleted."})
            except Exception as e:
                logger.error(f"Error deleting file {filename}: {e}")
                self.set_status(500)
                self.write({"status": "error", "message": str(e)})
        else:
            self.set_status(403)
            self.write({"status": "error", "message": "Permission denied."})

    def can_user_delete(self, filename, user_id):
        """
        Checks if the given user_id is allowed to delete the file.
        """
        try:
            with open(METADATA_FILE, 'r') as f:
                data = json.load(f)
            for entry in data:
                if entry['filename'] == filename:
                    return entry['user_id'] == user_id
        except Exception as e:
            logger.error(f"Error checking delete permission: {e}")
        return False

    def remove_metadata(self, filename):
        """
        Removes the metadata entry for the given filename.
        """
        try:
            with open(METADATA_FILE, 'r') as f:
                data = json.load(f)
            data = [entry for entry in data if entry['filename'] != filename]
            with open(METADATA_FILE, 'w') as f:
                json.dump(data, f, indent=4)
            logger.info(f"Metadata entry for {filename} removed.")
        except Exception as e:
            logger.error(f"Error removing metadata for {filename}: {e}")


# Application setup
def make_app():
    return tornado.web.Application([
        (r"/sections/upload.html", UploadHandler),       
        (r"/api/images", ImageListAPI),
        (r"/api/delete/(.*)", DeleteImageAPI),
        (r"/websocket", GalleryWebSocketHandler),
        (r"/uploads/(.*)", tornado.web.StaticFileHandler, {"path": UPLOADS_DIR}),
    ], cookie_secret="YOUR_SECRET_KEY", template_path="/var/www/html/prazskysvazjuda.cz/dist/sections/")

if __name__ == "__main__":
    os.makedirs(UPLOADS_DIR, exist_ok=True)
    os.makedirs(IMAGES_DIR, exist_ok=True)
    app = make_app()
    app.listen(8889)
    logger.info("Tornado server is running at http://localhost:8889")
    tornado.ioloop.IOLoop.current().start()
