import grpc
import time
import sqlite3
import random
from concurrent import futures

# Correct direct imports (since files are next to analytics_server.py)
import weather_pb2 as pb2
import weather_pb2_grpc as pb2_grpc

# Expected secret key for authentication
VALID_API_TOKEN = "SECRET_WEATHER_KEY_2025"
REPORT_INTERVAL_SECONDS = 5
DATABASE_NAME = 'weather_data.db'

# --- 1. SQLite Database Setup (Optional requirement) ---
def init_db():
    conn = sqlite3.connect(DATABASE_NAME)
    cursor = conn.cursor()
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS weather_stats (
            id INTEGER PRIMARY KEY,
            avg_temp REAL,
            avg_humidity REAL,
            avg_pressure REAL,
            report_time INTEGER
        )
    """)
    conn.commit()
    conn.close()

# --- 2. Analytics Service Logic ---
class AnalyticsServicer(pb2_grpc.WeatherAnalyticsServiceServicer):

    def __init__(self):
        super().__init__()
        # Variable to store the last sent stat (for Unary RPC)
        self.last_stats = pb2.WeatherStats(avg_temperature=0, avg_humidity=0, avg_pressure=0)
        # Queue list for Dashboard clients subscribers
        self.dashboard_subscribers = []

    # --- Helper function for Authentication ---
    def _authenticate(self, context):
        """Checks for the correct authentication key in Metadata."""

        # Get metadata from the context
        metadata = dict(context.invocation_metadata())

        # gRPC converts all keys to lowercase
        auth_token = metadata.get('authorization')

        # The token must be in the format: Bearer <TOKEN>
        if auth_token and auth_token.startswith('Bearer '):
            token = auth_token.split('Bearer ')[1]
            if token == VALID_API_TOKEN:
                print("✅ Authentication successful.")
                return True

        # If authentication fails, abort the connection with UNAUTHENTICATED error
        print("❌ Authentication failed. Connection refused.")
        context.abort(
            grpc.StatusCode.UNAUTHENTICATED,
            "Authentication failed. Invalid or missing API token."
        )
        return False

    # A. Client Streaming: Receiving Data from Sensor
    def StreamWeatherData(self, request_iterator, context):

        # 1. Authenticate first
        if not self._authenticate(context):
            return

        print(f"\n--- Starting to receive sensor data from: {context.peer()} ---")

        # Variables for average calculation
        temps, hums, press = [], [], []
        count = 0

        for data in request_iterator:
            count += 1
            temps.append(data.temperature_celsius)
            hums.append(data.humidity_percent)
            press.append(data.pressure_hpa)

            # 2. Boundary Check and Status Error Trigger (Alert)
            if data.temperature_celsius > 45:
                # Abort the client stream with an error to signal an alert
                context.abort(
                    grpc.StatusCode.RESOURCE_EXHAUSTED,
                    "ALERT: Extreme temperature detected! System overloading."
                )
                return self.last_stats # Must return something even if the connection fails

            # 3. Calculate average and send to subscribers every 5 readings
            if count % 5 == 0 and count > 0:
                avg_stats = pb2.WeatherStats(
                    avg_temperature=sum(temps) / len(temps),
                    avg_humidity=sum(hums) / len(hums),
                    avg_pressure=sum(press) / len(press),
                    report_time=int(time.time()),
                )

                print(f"  > Analysis: Temp Avg={avg_stats.avg_temperature:.2f} (Saved locally).")
                self.save_report(avg_stats)
                self.last_stats = avg_stats

                # Send the report to all Dashboard subscribers
                for subscriber_queue in self.dashboard_subscribers:
                    subscriber_queue.append(avg_stats)

                # Reset variables
                temps, hums, press = [], [], []

        print("--- Sensor data stream finished ---")

        # Return the last stat upon client stream completion
        return self.last_stats

    # B. Server Streaming: Sending Analysis to Dashboard
    def StreamAnalytics(self, request, context):
        # 1. Add client to the subscriber list
        # We use a local list as a "queue" for each subscribed client
        report_queue = []
        self.dashboard_subscribers.append(report_queue)
        print(f"🟢 New Dashboard client subscribed to analytics reports. Total subscribers: {len(self.dashboard_subscribers)}")

        try:
            # 2. Wait and Send loop
            while True:
                # If there are new reports in the queue, send them
                if report_queue:
                    stats = report_queue.pop(0)
                    yield stats
                else:
                    # Wait briefly to avoid resource consumption
                    time.sleep(1)

                # If the client terminates the connection, exit the loop
                if context.is_active() == False:
                    break

        finally:
            # 3. Remove client upon disconnection
            if report_queue in self.dashboard_subscribers:
                self.dashboard_subscribers.remove(report_queue)
                print(f"🔴 Dashboard client disconnected. Total subscribers: {len(self.dashboard_subscribers)}")

    # C. Unary RPC: Retrieve Last Stored Report (Optional Requirement)
    def GetLastReport(self, request, context):
        print(f"🔵 Last report requested manually by: {context.peer()}")

        # Read from the database
        conn = sqlite3.connect(DATABASE_NAME)
        cursor = conn.cursor()

        cursor.execute("SELECT avg_temp, avg_humidity, avg_pressure, report_time FROM weather_stats ORDER BY id DESC LIMIT 1")
        row = cursor.fetchone()
        conn.close()

        if row:
            return pb2.WeatherStats(
                avg_temperature=row[0],
                avg_humidity=row[1],
                avg_pressure=row[2],
                report_time=row[3]
            )
        else:
             # If no data is available, return an appropriate error
             context.abort(grpc.StatusCode.NOT_FOUND, "No reports available in the database.")


    # --- Helper function for Data Storage (SQLite) ---
    def save_report(self, stats):
        conn = sqlite3.connect(DATABASE_NAME)
        cursor = conn.cursor()
        cursor.execute(
            "INSERT INTO weather_stats (avg_temp, avg_humidity, avg_pressure, report_time) VALUES (?, ?, ?, ?)",
            (stats.avg_temperature, stats.avg_humidity, stats.avg_pressure, stats.report_time)
        )
        conn.commit()
        conn.close()


# --- 3. Run the Server ---
def serve():
    init_db()  # Initialize the database before starting
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))

    # Bind the service implementation to the server
    pb2_grpc.add_WeatherAnalyticsServiceServicer_to_server(
        AnalyticsServicer(), server
    )

    # Run the server on the specified port (50051)
    port = '50051'
    server.add_insecure_port(f'[::]:{port}')
    server.start()
    print(f"✨ Analytics Server (Python) running on port {port}")

    # Keep the server running
    try:
        while True:
            time.sleep(86400) # Sleep for a day
    except KeyboardInterrupt:
        server.stop(0)
        print("Analytics Server stopped.")


if __name__ == '__main__':
    # Run the server
    serve()