# -*- coding: utf-8 -*-

from typing import List

from database import engine
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from models import Device, Project, Task, TaskWithProject
from sqlmodel import Session, SQLModel, select

# init fastapi
app: FastAPI = FastAPI()

# init CORS
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"])

# mount api endpoint
api: FastAPI = FastAPI(title="Scitizen")
app.mount("/api", api)


@app.on_event("startup")
def on_startup() -> None:
    """Setup event on app.

    It is used to create the database and the tables.
    """

    SQLModel.metadata.create_all(engine)


@api.post("/devices/{device_uuid}", response_model=Device)
def upsert_device(device_uuid: str, device: Device) -> Device:
    """Upsert a device.

    It is used to create a device in the database if it does not already exists,
    else it is used to update the existing one.

    Args:
      device_uuid:
        The uuid of the device to upsert.
      device:
        The device data.

    Returns:
      The upserted device.
    """

    with Session(engine) as session:
        # check if the device exists
        statement = select(Device).where(Device.uuid == device_uuid)
        result = session.exec(statement).first()

        # if not, create it
        if result is None:
            result = device

        # sync the data
        for key, value in device.dict(exclude_unset=True).items():
            setattr(result, key, value)

        # persist the data to the database
        session.add(result)
        session.commit()
        session.refresh(result)

        return result


@api.get("/devices/{device_uuid}", response_model=Device)
def select_device(device_uuid: str):
    """Select a device.

    It is used to get a device data from the database.

    Args:
      device_uuid:
        The uuid of the device to get the data from.

    Returns:
      The device data.
    """

    with Session(engine) as session:
        statement = select(Device).where(Device.uuid == device_uuid)
        result = session.exec(statement).first()
        return result


@api.get("/devices", response_model=List[Device])
def select_devices():
    """Select all devices.

    It is used to get all devices data from the database.

    Returns:
      All devices data.
    """

    with Session(engine) as session:
        statement = select(Device)
        results = session.exec(statement).all()
        return results


@api.post("/projects/{project_uuid}", response_model=Project)
def upsert_project(project_uuid: str, project: Project) -> Project:
    """Upsert a project.

    It is used to create a project in the database if it does not already exists,
    else it is used to update the existing one.

    Args:
      project_uuid:
        The uuid of the project to upsert.
      project:
        The project data.

    Returns:
      The upserted project.
    """

    with Session(engine) as session:
        # check if the project exists
        statement = select(Project).where(Project.uuid == project_uuid)
        result = session.exec(statement).first()

        # if not, create it
        if result is None:
            result = project

        # sync the data
        for key, value in project.dict(exclude_unset=True).items():
            setattr(result, key, value)

        # persist the data to the database
        session.add(result)
        session.commit()
        session.refresh(result)

        return result


@api.get("/projects/{project_uuid}", response_model=Project)
def select_project(project_uuid: str):
    """Select a project.

    It is used to get a project data from the database.

    Args:
      project_uuid:
        The uuid of the project to get the data from.

    Returns:
      The project data.
    """

    with Session(engine) as session:
        statement = select(Project).where(Project.uuid == project_uuid)
        result = session.exec(statement).first()
        return result


@api.get("/projects", response_model=List[Project])
def select_projects():
    """Select all projects.

    It is used to get all projects data from the database.

    Returns:
      All projects data.
    """

    with Session(engine) as session:
        statement = select(Project)
        results = session.exec(statement).all()
        return results


@api.post("/tasks/{task_uuid}", response_model=Task)
def upsert_task(task_uuid: str, task: Task) -> Task:
    """Upsert a task.

    It is used to create a task in the database if it does not already exists,
    else it is used to update the existing one.

    Args:
      task_uuid:
        The uuid of the task to upsert.
      task:
        The task data.

    Returns:
      The upserted task.
    """

    with Session(engine) as session:
        # check if the task exists
        statement = select(Task).where(Task.uuid == task_uuid)
        result = session.exec(statement).first()

        # if not, create it
        if result is None:
            result = task

        # sync the data
        for key, value in task.dict(exclude_unset=True).items():
            setattr(result, key, value)

        # persist the data to the database
        session.add(result)
        session.commit()
        session.refresh(result)

        return result


@api.get("/tasks/{task_uuid}", response_model=TaskWithProject)
def select_task(task_uuid: str):
    """Select a task.

    It is used to get a task data from the database.

    Args:
      task_uuid:
        The uuid of the task to get the data from.

    Returns:
      The task data.
    """

    with Session(engine) as session:
        statement = select(Task, Project).join(Project).where(Task.uuid == task_uuid)
        task, project = session.exec(statement).first()  # type: ignore
        result = TaskWithProject()
        for key, value in task.dict().items():
            setattr(result, key, value)
        result.project = project
        return result


@api.get("/tasks", response_model=List[TaskWithProject])
def select_tasks():
    """Select all tasks.

    It is used to get all tasks data from the database.

    Returns:
      All tasks data.
    """

    with Session(engine) as session:
        statement = select(Task, Project).join(Project)
        results = session.exec(statement).all()
        tasks = []
        for task, project in results:
            result = TaskWithProject()
            for key, value in task.dict().items():
                setattr(result, key, value)
            result.project = project
            tasks.append(result)
        return tasks


@api.put("/tasks/clean")
def clean_tasks():
    """Clean all tasks.

    It is used to run maintenance queries on the database in order to keep
    consistent data on tasks.
    """

    with Session(engine) as session:
        with open("./data/clean_failed_tasks.sql", "r", encoding="utf-8") as stream:
            statement = stream.read()
        session.exec(statement)

        with open("./data/clean_succeeded_tasks.sql", "r", encoding="utf-8") as stream:
            statement = stream.read()
        session.exec(statement)

        session.commit()

sqlmodel.SQLModel.metadata.create_all sqlmodel.Session models.TaskWithProject fastapi.FastAPI sqlmodel.select
[((293, 302), 'fastapi.FastAPI', 'FastAPI', ([], {}), '()\n', (300, 302), False, 'from fastapi import FastAPI\n'), ((430, 455), 'fastapi.FastAPI', 'FastAPI', ([], {'title': '"""Scitizen"""'}), "(title='Scitizen')\n", (437, 455), False, 'from fastapi import FastAPI\n'), ((627, 663), 'sqlmodel.SQLModel.metadata.create_all', 'SQLModel.metadata.create_all', (['engine'], {}), '(engine)\n', (655, 663), False, 'from sqlmodel import Session, SQLModel, select\n'), ((1112, 1127), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (1119, 1127), False, 'from sqlmodel import Session, SQLModel, select\n'), ((1978, 1993), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (1985, 1993), False, 'from sqlmodel import Session, SQLModel, select\n'), ((2362, 2377), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (2369, 2377), False, 'from sqlmodel import Session, SQLModel, select\n'), ((2410, 2424), 'sqlmodel.select', 'select', (['Device'], {}), '(Device)\n', (2416, 2424), False, 'from sqlmodel import Session, SQLModel, select\n'), ((2959, 2974), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (2966, 2974), False, 'from sqlmodel import Session, SQLModel, select\n'), ((3841, 3856), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (3848, 3856), False, 'from sqlmodel import Session, SQLModel, select\n'), ((4234, 4249), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (4241, 4249), False, 'from sqlmodel import Session, SQLModel, select\n'), ((4282, 4297), 'sqlmodel.select', 'select', (['Project'], {}), '(Project)\n', (4288, 4297), False, 'from sqlmodel import Session, SQLModel, select\n'), ((4787, 4802), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (4794, 4802), False, 'from sqlmodel import Session, SQLModel, select\n'), ((5632, 5647), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (5639, 5647), False, 'from sqlmodel import Session, SQLModel, select\n'), ((5835, 5852), 'models.TaskWithProject', 'TaskWithProject', ([], {}), '()\n', (5850, 5852), False, 'from models import Device, Project, Task, TaskWithProject\n'), ((6210, 6225), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (6217, 6225), False, 'from sqlmodel import Session, SQLModel, select\n'), ((6818, 6833), 'sqlmodel.Session', 'Session', (['engine'], {}), '(engine)\n', (6825, 6833), False, 'from sqlmodel import Session, SQLModel, select\n'), ((6420, 6437), 'models.TaskWithProject', 'TaskWithProject', ([], {}), '()\n', (6435, 6437), False, 'from models import Device, Project, Task, TaskWithProject\n'), ((1197, 1211), 'sqlmodel.select', 'select', (['Device'], {}), '(Device)\n', (1203, 1211), False, 'from sqlmodel import Session, SQLModel, select\n'), ((2026, 2040), 'sqlmodel.select', 'select', (['Device'], {}), '(Device)\n', (2032, 2040), False, 'from sqlmodel import Session, SQLModel, select\n'), ((3045, 3060), 'sqlmodel.select', 'select', (['Project'], {}), '(Project)\n', (3051, 3060), False, 'from sqlmodel import Session, SQLModel, select\n'), ((3889, 3904), 'sqlmodel.select', 'select', (['Project'], {}), '(Project)\n', (3895, 3904), False, 'from sqlmodel import Session, SQLModel, select\n'), ((4870, 4882), 'sqlmodel.select', 'select', (['Task'], {}), '(Task)\n', (4876, 4882), False, 'from sqlmodel import Session, SQLModel, select\n'), ((6258, 6279), 'sqlmodel.select', 'select', (['Task', 'Project'], {}), '(Task, Project)\n', (6264, 6279), False, 'from sqlmodel import Session, SQLModel, select\n'), ((5680, 5701), 'sqlmodel.select', 'select', (['Task', 'Project'], {}), '(Task, Project)\n', (5686, 5701), False, 'from sqlmodel import Session, SQLModel, select\n')]