"""
FastAPI Web Server with REST API and WebSocket support
"""

import os
import json
import asyncio
import logging
from datetime import datetime
from typing import Dict, Any, List, Optional
from contextlib import asynccontextmanager

from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, BackgroundTasks
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

from .rtc import RTCManager
from .watchdog import WatchDogManager

logger = logging.getLogger(__name__)

# Pydantic models for API
class TimeSetRequest(BaseModel):
    hour: int
    minute: int
    second: int

class DateSetRequest(BaseModel):
    year: int
    month: int
    day: int

class DateTimeSetRequest(BaseModel):
    year: int
    month: int
    day: int
    hour: int
    minute: int
    second: int

class RTCStatus(BaseModel):
    available: bool
    mock: bool
    i2c_address: str
    i2c_bus: int
    time: Optional[str] = None
    date: Optional[str] = None
    temperature: Optional[float] = None

class WatchDogStatus(BaseModel):
    available: bool
    mock: bool
    i2c_address: str
    i2c_bus: int
    gpio_pin: int
    timeout: int
    version: Optional[int] = None

class SystemStatus(BaseModel):
    rtc: RTCStatus
    watchdog: WatchDogStatus
    timestamp: str

# Global managers
rtc_manager: Optional[RTCManager] = None
watchdog_manager: Optional[WatchDogManager] = None

# WebSocket connection manager
class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []
    
    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
    
    def disconnect(self, websocket: WebSocket):
        if websocket in self.active_connections:
            self.active_connections.remove(websocket)
    
    async def broadcast(self, message: Dict[str, Any]):
        disconnected = []
        for connection in self.active_connections:
            try:
                await connection.send_json(message)
            except:
                disconnected.append(connection)
        
        # Remove disconnected clients
        for conn in disconnected:
            if conn in self.active_connections:
                self.active_connections.remove(conn)

manager = ConnectionManager()

@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan handler"""
    # Startup
    global rtc_manager, watchdog_manager
    
    mock_mode = os.environ.get('RTC_MOCK', 'false').lower() == 'true'
    rtc_addr = int(os.environ.get('RTC_I2C_ADDRESS', '0x68'), 16)
    wd_addr = int(os.environ.get('WATCHDOG_I2C_ADDRESS', '0x67'), 16)
    wd_gpio = int(os.environ.get('WATCHDOG_GPIO_PIN', '4'))
    
    rtc_manager = RTCManager(i2c_address=rtc_addr, mock=mock_mode)
    watchdog_manager = WatchDogManager(i2c_address=wd_addr, gpio_pin=wd_gpio, mock=mock_mode)
    
    # Start background broadcast task
    asyncio.create_task(broadcast_updates())
    
    logger.info("piRTC API server started")
    yield
    
    # Shutdown
    logger.info("piRTC API server stopped")

# Create FastAPI app
app = FastAPI(
    title="piRTC API",
    description="REST API and WebSocket for RPi RTC & WatchDog management",
    version="1.0.0",
    lifespan=lifespan
)

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Static files (web panel)
static_dir = os.path.join(os.path.dirname(__file__), 'web')
if os.path.exists(static_dir):
    app.mount("/static", StaticFiles(directory=static_dir), name="static")

async def broadcast_updates():
    """Background task to broadcast updates to WebSocket clients"""
    while True:
        if manager.active_connections and rtc_manager:
            try:
                status = get_system_status_dict()
                await manager.broadcast({
                    "type": "status_update",
                    "data": status,
                    "timestamp": datetime.now().isoformat()
                })
            except Exception as e:
                logger.error(f"Broadcast error: {e}")
        
        await asyncio.sleep(5)  # Update every 5 seconds

def get_system_status_dict() -> Dict[str, Any]:
    """Get system status as dictionary"""
    return {
        "rtc": rtc_manager.get_status() if rtc_manager else {"available": False},
        "watchdog": watchdog_manager.get_status() if watchdog_manager else {"available": False},
        "timestamp": datetime.now().isoformat()
    }

# ========== REST API Endpoints ==========

@app.get("/", response_class=HTMLResponse)
async def root():
    """Serve web panel"""
    html_content = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>piRTC Web Panel</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            color: #333;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        header {
            text-align: center;
            padding: 40px 0;
            color: white;
        }
        header h1 { font-size: 3em; margin-bottom: 10px; }
        header p { font-size: 1.2em; opacity: 0.9; }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
            margin-top: 30px;
        }
        .card {
            background: white;
            border-radius: 12px;
            padding: 25px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            transition: transform 0.3s ease;
        }
        .card:hover { transform: translateY(-5px); }
        .card h2 {
            color: #667eea;
            margin-bottom: 20px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        .status-item {
            display: flex;
            justify-content: space-between;
            padding: 12px 0;
            border-bottom: 1px solid #eee;
        }
        .status-item:last-child { border-bottom: none; }
        .status-value {
            font-weight: 600;
            color: #667eea;
        }
        .time-display {
            font-size: 2em;
            text-align: center;
            color: #333;
            margin: 20px 0;
            font-family: 'Courier New', monospace;
        }
        .temp-display {
            text-align: center;
            font-size: 1.5em;
            color: #e74c3c;
        }
        .btn {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 1em;
            transition: opacity 0.3s;
            margin: 5px;
        }
        .btn:hover { opacity: 0.9; }
        .btn:active { transform: scale(0.98); }
        .input-group {
            display: flex;
            gap: 10px;
            margin: 10px 0;
            flex-wrap: wrap;
        }
        .input-group input {
            flex: 1;
            min-width: 60px;
            padding: 10px;
            border: 2px solid #ddd;
            border-radius: 6px;
            font-size: 1em;
        }
        .input-group input:focus {
            outline: none;
            border-color: #667eea;
        }
        .ws-status {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 10px 20px;
            border-radius: 20px;
            color: white;
            font-weight: 600;
        }
        .ws-connected { background: #27ae60; }
        .ws-disconnected { background: #e74c3c; }
        .log {
            background: #f8f9fa;
            border-radius: 6px;
            padding: 15px;
            margin-top: 15px;
            max-height: 200px;
            overflow-y: auto;
            font-family: 'Courier New', monospace;
            font-size: 0.9em;
        }
        .log-entry { margin: 5px 0; }
        .log-time { color: #667eea; }
    </style>
</head>
<body>
    <div id="ws-status" class="ws-status ws-disconnected">WebSocket: Disconnected</div>
    
    <div class="container">
        <header>
            <h1>⏰ piRTC</h1>
            <p>Raspberry Pi RTC & WatchDog Management</p>
        </header>
        
        <div class="grid">
            <div class="card">
                <h2>🕐 RTC Time</h2>
                <div id="rtc-time" class="time-display">--:--:--</div>
                <div id="rtc-date" style="text-align: center; color: #666; margin-bottom: 20px;">----/--/--</div>
                <div class="input-group">
                    <input type="number" id="h" placeholder="HH" min="0" max="23">
                    <input type="number" id="m" placeholder="MM" min="0" max="59">
                    <input type="number" id="s" placeholder="SS" min="0" max="59">
                </div>
                <div class="input-group">
                    <input type="number" id="y" placeholder="YYYY" min="2000" max="2099">
                    <input type="number" id="mo" placeholder="MM" min="1" max="12">
                    <input type="number" id="d" placeholder="DD" min="1" max="31">
                </div>
                <button class="btn" onclick="setDateTime()">Set Date & Time</button>
                <button class="btn" onclick="syncFromSystem()">Sync from System</button>
                <button class="btn" onclick="syncToSystem()">Sync to System</button>
            </div>
            
            <div class="card">
                <h2>🌡️ RTC Temperature</h2>
                <div id="rtc-temp" class="temp-display">--.-°C</div>
                <div class="status-item">
                    <span>Status</span>
                    <span id="rtc-status" class="status-value">Unknown</span>
                </div>
                <div class="status-item">
                    <span>I2C Address</span>
                    <span id="rtc-addr" class="status-value">0x68</span>
                </div>
            </div>
            
            <div class="card">
                <h2>🐕 WatchDog</h2>
                <div class="status-item">
                    <span>Status</span>
                    <span id="wd-status" class="status-value">Unknown</span>
                </div>
                <div class="status-item">
                    <span>Version</span>
                    <span id="wd-version" class="status-value">--</span>
                </div>
                <div class="status-item">
                    <span>GPIO Pin</span>
                    <span id="wd-gpio" class="status-value">4</span>
                </div>
                <div class="status-item">
                    <span>Timeout</span>
                    <span id="wd-timeout" class="status-value">60s</span>
                </div>
                <button class="btn" onclick="feedWatchDog()">Feed WatchDog</button>
                <button class="btn" onclick="testWatchDog()">Test Feed Loop</button>
            </div>
            
            <div class="card">
                <h2>📊 System Log</h2>
                <div id="log" class="log">
                    <div class="log-entry"><span class="log-time">System</span> Web panel loaded</div>
                </div>
                <button class="btn" onclick="clearLog()">Clear Log</button>
            </div>
        </div>
    </div>

    <script>
        let ws = null;
        const logEl = document.getElementById('log');
        const wsStatusEl = document.getElementById('ws-status');
        
        function log(msg) {
            const time = new Date().toLocaleTimeString();
            logEl.innerHTML += `<div class="log-entry"><span class="log-time">${time}</span> ${msg}</div>`;
            logEl.scrollTop = logEl.scrollHeight;
        }
        
        function clearLog() {
            logEl.innerHTML = '';
        }
        
        function connectWebSocket() {
            ws = new WebSocket(`ws://${window.location.host}/ws`);
            
            ws.onopen = () => {
                wsStatusEl.textContent = 'WebSocket: Connected';
                wsStatusEl.className = 'ws-status ws-connected';
                log('WebSocket connected');
            };
            
            ws.onclose = () => {
                wsStatusEl.textContent = 'WebSocket: Disconnected';
                wsStatusEl.className = 'ws-status ws-disconnected';
                log('WebSocket disconnected');
                setTimeout(connectWebSocket, 3000);
            };
            
            ws.onerror = (err) => {
                log('WebSocket error');
                console.error(err);
            };
            
            ws.onmessage = (event) => {
                const data = JSON.parse(event.data);
                if (data.type === 'status_update') {
                    updateUI(data.data);
                }
            };
        }
        
        function updateUI(status) {
            // RTC
            if (status.rtc) {
                document.getElementById('rtc-status').textContent = 
                    status.rtc.available ? (status.rtc.mock ? 'Mock' : 'Active') : 'Offline';
                if (status.rtc.time) document.getElementById('rtc-time').textContent = status.rtc.time;
                if (status.rtc.date) document.getElementById('rtc-date').textContent = status.rtc.date;
                if (status.rtc.temperature !== undefined) {
                    document.getElementById('rtc-temp').textContent = status.rtc.temperature.toFixed(1) + '°C';
                }
            }
            
            // WatchDog
            if (status.watchdog) {
                document.getElementById('wd-status').textContent = 
                    status.watchdog.available ? (status.watchdog.mock ? 'Mock' : 'Active') : 'Offline';
                if (status.watchdog.version !== undefined) {
                    document.getElementById('wd-version').textContent = status.watchdog.version;
                }
            }
        }
        
        async function setDateTime() {
            const year = parseInt(document.getElementById('y').value);
            const month = parseInt(document.getElementById('mo').value);
            const day = parseInt(document.getElementById('d').value);
            const hour = parseInt(document.getElementById('h').value);
            const minute = parseInt(document.getElementById('m').value);
            const second = parseInt(document.getElementById('s').value);
            
            if (!year || !month || !day || hour === null || minute === null || second === null) {
                alert('Please fill all fields');
                return;
            }
            
            try {
                const res = await fetch('/api/rtc/datetime', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json'},
                    body: JSON.stringify({year, month, day, hour, minute, second})
                });
                const data = await res.json();
                if (data.success) {
                    log('Date & time set successfully');
                } else {
                    log('Failed to set date & time');
                }
            } catch (err) {
                log('Error: ' + err.message);
            }
        }
        
        async function syncFromSystem() {
            try {
                const res = await fetch('/api/rtc/sync-from-system', {method: 'POST'});
                const data = await res.json();
                log(data.success ? 'Synced from system time' : 'Sync failed');
            } catch (err) {
                log('Error: ' + err.message);
            }
        }
        
        async function syncToSystem() {
            try {
                const res = await fetch('/api/rtc/sync-to-system', {method: 'POST'});
                const data = await res.json();
                log(data.success ? 'Synced to system time' : 'Sync failed');
            } catch (err) {
                log('Error: ' + err.message);
            }
        }
        
        async function feedWatchDog() {
            try {
                const res = await fetch('/api/watchdog/feed', {method: 'POST'});
                const data = await res.json();
                log(data.success ? 'WatchDog fed' : 'Feed failed');
            } catch (err) {
                log('Error: ' + err.message);
            }
        }
        
        async function testWatchDog() {
            log('Starting WatchDog test loop...');
            for (let i = 0; i < 5; i++) {
                await feedWatchDog();
                await new Promise(r => setTimeout(r, 2000));
            }
            log('Test loop completed');
        }
        
        // Initial load
        fetch('/api/status')
            .then(r => r.json())
            .then(data => updateUI(data))
            .catch(err => log('Failed to load status'));
        
        connectWebSocket();
    </script>
</body>
</html>
    """
    return HTMLResponse(content=html_content)

@app.get("/api/status", response_model=SystemStatus)
async def get_status():
    """Get complete system status"""
    return get_system_status_dict()

# RTC API Endpoints
@app.get("/api/rtc/status", response_model=RTCStatus)
async def get_rtc_status():
    """Get RTC status"""
    if not rtc_manager:
        raise HTTPException(status_code=503, detail="RTC not initialized")
    return rtc_manager.get_status()

@app.get("/api/rtc/time")
async def get_rtc_time():
    """Get RTC time"""
    if not rtc_manager:
        raise HTTPException(status_code=503, detail="RTC not initialized")
    
    time_data = rtc_manager.read_time()
    if not time_data:
        raise HTTPException(status_code=500, detail="Failed to read RTC time")
    
    return {"time": time_data, "success": True}

@app.get("/api/rtc/date")
async def get_rtc_date():
    """Get RTC date"""
    if not rtc_manager:
        raise HTTPException(status_code=503, detail="RTC not initialized")
    
    date_data = rtc_manager.read_date()
    if not date_data:
        raise HTTPException(status_code=500, detail="Failed to read RTC date")
    
    return {"date": date_data, "success": True}

@app.get("/api/rtc/temperature")
async def get_rtc_temperature():
    """Get RTC temperature"""
    if not rtc_manager:
        raise HTTPException(status_code=503, detail="RTC not initialized")
    
    temp_data = rtc_manager.read_temperature()
    if not temp_data:
        raise HTTPException(status_code=500, detail="Failed to read temperature")
    
    return {"temperature": temp_data, "success": True}

@app.post("/api/rtc/time")
async def set_rtc_time(request: TimeSetRequest):
    """Set RTC time"""
    if not rtc_manager:
        raise HTTPException(status_code=503, detail="RTC not initialized")
    
    success = rtc_manager.set_time(request.hour, request.minute, request.second)
    return {"success": success}

@app.post("/api/rtc/date")
async def set_rtc_date(request: DateSetRequest):
    """Set RTC date"""
    if not rtc_manager:
        raise HTTPException(status_code=503, detail="RTC not initialized")
    
    success = rtc_manager.set_date(request.year, request.month, request.day)
    return {"success": success}

@app.post("/api/rtc/datetime")
async def set_rtc_datetime(request: DateTimeSetRequest):
    """Set RTC date and time"""
    if not rtc_manager:
        raise HTTPException(status_code=503, detail="RTC not initialized")
    
    from datetime import datetime
    dt = datetime(request.year, request.month, request.day,
                  request.hour, request.minute, request.second)
    success = rtc_manager.set_datetime(dt)
    return {"success": success}

@app.post("/api/rtc/sync-to-system")
async def sync_rtc_to_system():
    """Sync RTC time to system"""
    if not rtc_manager:
        raise HTTPException(status_code=503, detail="RTC not initialized")
    
    success = rtc_manager.sync_to_system()
    return {"success": success}

@app.post("/api/rtc/sync-from-system")
async def sync_rtc_from_system():
    """Sync system time to RTC"""
    if not rtc_manager:
        raise HTTPException(status_code=503, detail="RTC not initialized")
    
    success = rtc_manager.sync_from_system()
    return {"success": success}

# WatchDog API Endpoints
@app.get("/api/watchdog/status", response_model=WatchDogStatus)
async def get_watchdog_status():
    """Get WatchDog status"""
    if not watchdog_manager:
        raise HTTPException(status_code=503, detail="WatchDog not initialized")
    return watchdog_manager.get_status()

@app.post("/api/watchdog/feed")
async def feed_watchdog():
    """Feed the watchdog"""
    if not watchdog_manager:
        raise HTTPException(status_code=503, detail="WatchDog not initialized")
    
    success = watchdog_manager.feed()
    return {"success": success}

# WebSocket Endpoint
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    """WebSocket for real-time updates"""
    await manager.connect(websocket)
    
    try:
        # Send initial status
        await websocket.send_json({
            "type": "status_update",
            "data": get_system_status_dict(),
            "timestamp": datetime.now().isoformat()
        })
        
        while True:
            # Receive commands from client
            data = await websocket.receive_json()
            
            if data.get("action") == "feed_watchdog":
                if watchdog_manager:
                    success = watchdog_manager.feed()
                    await websocket.send_json({
                        "type": "command_response",
                        "command": "feed_watchdog",
                        "success": success
                    })
            
            elif data.get("action") == "get_status":
                await websocket.send_json({
                    "type": "status_update",
                    "data": get_system_status_dict(),
                    "timestamp": datetime.now().isoformat()
                })
    
    except WebSocketDisconnect:
        manager.disconnect(websocket)
    except Exception as e:
        logger.error(f"WebSocket error: {e}")
        manager.disconnect(websocket)

def main():
    """Run the API server"""
    import uvicorn
    
    host = os.environ.get('API_HOST', '0.0.0.0')
    port = int(os.environ.get('API_PORT', '8080'))
    
    uvicorn.run(app, host=host, port=port)

if __name__ == "__main__":
    main()
