from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional, Dict, Any
from datetime import datetime
import traceback
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from io import BytesIO
import os
import uvicorn
import sys
root_path = os.getcwd()
sys.path.append(root_path)

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
app = FastAPI()


# BRDF data item model
class BRDFItem(BaseModel):
    sampleModel: str
    temperatureK: float
    wavelengthUm: str
    thetaIncident: float
    phiIncident: float
    thetaReflected: float
    phiReflected: float
    measurement1: float
    measurement2: float
    measurement3: float
    measurement4: float
    measurement5: float
    meanValue: float
    repeatabilityPct: float


# Request payload model
class RequestPayload(BaseModel):
    filePath: str
    brdfList: List[BRDFItem]


# Response model
class ResponseModel(BaseModel):
    status: int
    msg: str
    filePath: Optional[str] = None


# Global exception handler
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
    print(f"Unhandled exception: {str(exc)}")
    print(traceback.format_exc())
    return {"status": 500, "msg": "Failed"}


def sph2cart(theta: float, phi: float):
    x = np.sin(theta) * np.cos(phi)
    y = np.sin(theta) * np.sin(phi)
    z = np.cos(theta)
    return x, y, z


def plot_all_hemispheres(brdf_list: List[BRDFItem], save_path: str):
    os.makedirs(os.path.dirname(save_path), exist_ok=True)

    # Generate hemisphere surface
    phi = np.linspace(0, 2 * np.pi, 60)
    theta = np.linspace(0, np.pi / 2, 30)
    PHI, THETA = np.meshgrid(phi, theta)
    X = np.sin(THETA) * np.cos(PHI)
    Y = np.sin(THETA) * np.sin(PHI)
    Z = np.cos(THETA)

    # Create 3D plot
    fig = plt.figure(figsize=(10, 8))
    ax = fig.add_subplot(111, projection='3d')
    ax.plot_surface(X, Y, Z, rstride=5, cstride=5, color='lightblue', alpha=0.1, edgecolor='k')

    # Draw vectors for each BRDF data item
    for i, item in enumerate(brdf_list):
        # Convert spherical to cartesian coordinates
        xi, yi, zi = sph2cart(np.deg2rad(item.thetaIncident), np.deg2rad(item.phiIncident))
        xr, yr, zr = sph2cart(np.deg2rad(item.thetaReflected), np.deg2rad(item.phiReflected))

        # Assign colors
        color_i = plt.cm.tab10(i % 10)  # Incident light color
        color_r = plt.cm.tab10((i + 5) % 10)  # Reflected light color

        # Incident light - points inward (toward center)
        ax.quiver(xi, yi, zi, -xi, -yi, -zi, color=color_i, linewidth=2, arrow_length_ratio=0.1)

        # Reflected light - points outward (away from center)
        ax.quiver(0, 0, 0, xr, yr, zr, color=color_r, linewidth=2, arrow_length_ratio=0.1)

    # Set labels and limits
    ax.set_xlim([-1, 1])
    ax.set_ylim([-1, 1])
    ax.set_zlim([0, 1])
    ax.set_xlabel('sinθ·cosφ')
    ax.set_ylabel('sinθ·sinφ')
    ax.set_zlabel('Z')
    ax.set_title('BRDF扫描轨迹图')

    plt.tight_layout()
    plt.savefig(save_path, format='png', dpi=300)
    plt.close(fig)

    buf = BytesIO()
    plt.savefig(buf, format='png', dpi=300)
    buf.seek(0)
    return buf


@app.post("/process_brdf", response_model=ResponseModel)
async def process_brdf(payload: RequestPayload):
    """
    Process BRDF data endpoint

    Parameters:
    - filePath: File save path
    - brdfList: List of BRDF data items

    Returns:
    - status: Status code
    - msg: Message
    - filePath: Saved file path
    """
    try:
        if not payload.filePath:
            return {"status": 500, "msg": "File path is empty"}

        if not payload.brdfList:
            return {"status": 500, "msg": "BRDF list is empty"}

        # Plot all BRDF data
        buf = plot_all_hemispheres(payload.brdfList, payload.filePath)

        return {
            "status": 200,
            "msg": "Success",
            "filePath": payload.filePath
        }

    except Exception as e:
        print(f"Processing error: {str(e)}")
        print(traceback.format_exc())
        return {"status": 500, "msg": "Failed"}


# if __name__ == "__main__":
#     import uvicorn
#
#     uvicorn.run(app, host="0.0.0.0", port=8081)
# if __name__ == "__main__":
#     uvicorn.run("apiServer:app", host="0.0.0.0", port=8081, reload=False)
if __name__ == "__main__":
    # 更可靠的获取模块名方式
    name_app = os.path.splitext(os.path.basename(__file__))[0]

    # 更完整的日志配置
    log_config = {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": {
            "default": {
                "()": "uvicorn.logging.DefaultFormatter",
                "fmt": "%(levelprefix)s %(asctime)s %(message)s",
                "datefmt": "%Y-%m-%d %H:%M:%S",
            }
        },
        "handlers": {
            "default": {
                "formatter": "default",
                "class": "logging.StreamHandler",
                "stream": "ext://sys.stderr",
            },
            "file_handler": {
                "class": "logging.FileHandler",
                "filename": "logfile.log",
                "formatter": "default",
            },
        },
        "root": {
            "handlers": ["default", "file_handler"],
            "level": "INFO",
        },
    }

    uvicorn.run(
        app,
        host="0.0.0.0",
        port=8081,
        reload=False,
        log_config=log_config
    )