from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from http import HTTPStatus
from functools import partial
import os
import re

MEDIA_DIR = r"C:\Media"
HOST = "0.0.0.0"
PORT = 8080


class RangeRequestHandler(SimpleHTTPRequestHandler):

    def send_head(self):
        path = self.translate_path(self.path)

        # Let the normal handler deal with folders/directory listings.
        if os.path.isdir(path):
            return super().send_head()

        try:
            f = open(path, "rb")
        except OSError:
            self.send_error(HTTPStatus.NOT_FOUND, "File not found")
            return None

        file_size = os.fstat(f.fileno()).st_size
        content_type = self.guess_type(path)

        range_header = self.headers.get("Range")

        if range_header:
            match = re.match(r"bytes=(\d*)-(\d*)$", range_header.strip())

            if not match:
                f.close()
                self.send_error(
                    HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE,
                    "Invalid range"
                )
                return None

            start_text, end_text = match.groups()

            if start_text:
                start = int(start_text)
                end = int(end_text) if end_text else file_size - 1
            else:
                # Example: Range: bytes=-500
                suffix_length = int(end_text)
                start = max(0, file_size - suffix_length)
                end = file_size - 1

            if start >= file_size or start > end:
                f.close()
                self.send_response(HTTPStatus.REQUESTED_RANGE_NOT_SATISFIABLE)
                self.send_header("Content-Range", f"bytes */{file_size}")
                self.end_headers()
                return None

            end = min(end, file_size - 1)
            length = end - start + 1

            self.send_response(HTTPStatus.PARTIAL_CONTENT)
            self.send_header("Content-Type", content_type)
            self.send_header("Accept-Ranges", "bytes")
            self.send_header(
                "Content-Range",
                f"bytes {start}-{end}/{file_size}"
            )
            self.send_header("Content-Length", str(length))
            self.send_header(
                "Last-Modified",
                self.date_time_string(os.fstat(f.fileno()).st_mtime)
            )
            self.end_headers()

            self.range = (start, end)
            return f

        # Normal full-file request
        self.send_response(HTTPStatus.OK)
        self.send_header("Content-Type", content_type)
        self.send_header("Content-Length", str(file_size))
        self.send_header("Accept-Ranges", "bytes")
        self.send_header(
            "Last-Modified",
            self.date_time_string(os.fstat(f.fileno()).st_mtime)
        )
        self.end_headers()

        self.range = None
        return f

    def copyfile(self, source, outputfile):
        if getattr(self, "range", None):
            start, end = self.range
            source.seek(start)

            remaining = end - start + 1

            while remaining > 0:
                chunk = source.read(min(1024 * 1024, remaining))

                if not chunk:
                    break

                outputfile.write(chunk)
                remaining -= len(chunk)

        else:
            super().copyfile(source, outputfile)


handler = partial(
    RangeRequestHandler,
    directory=MEDIA_DIR
)

server = ThreadingHTTPServer(
    (HOST, PORT),
    handler
)

print(f"Media server running on port {PORT}")
print(f"Serving: {MEDIA_DIR}")
print("HTTP byte-range streaming enabled.")
print("Press Ctrl+C to stop.")

try:
    server.serve_forever()
except KeyboardInterrupt:
    print("\nStopping media server...")
finally:
    server.server_close()