"Presentation router"
import datetime

# from utils.XCM_logger import ServiceLogger
import logging
from typing import Annotated, List

from fastapi import (
    APIRouter,
    Body,
    Depends,
    File,
    HTTPException,
    Query,
    Request,
    UploadFile,
    status,
)
from fastapi.responses import FileResponse
from pydantic import error_wrappers

from models.team import Team
from services.ppt_generator.company_profile_slide import CompanyProfileSlide
from services.ppt_generator.data_classes.company_profile_ppt import CompanyProfilePPT
from services.ppt_generator.data_classes.project import Project, Section, Slide
from services.ppt_generator.structure import PPTStructure
from services.team_services import TeamService
from services.user_service import UserService
from utils.client_check import ClientConfig
from utils.document_loader.DocUploader import doc_uploader_call
from utils.document_loader.DocUploaderV2 import DocUploaderV2
from utils.dynamo_db import DynamoDB
from utils.url_parser import parsed_url

XCM_logger = logging.getLogger()


def get_team_config_data(team_id: str):
    "Get the team data"
    try:
        team = Team.get_team_by_id(team_id)
        if not team:
            raise HTTPException(status_code=404, detail={"message": "Team not found"})
        default_config = ClientConfig("default").init_default_with_custom_values(
            team["client_config"]
        )
        if default_config is None:
            raise HTTPException(
                status_code=404, detail={"message": "Team client config not found"}
            )

        return default_config
    except Exception as e:
        print("Error in get_team_config_data", e)


presentation = APIRouter(
    prefix="/presentation",
    tags=["presentation"],
    responses={404: {"description": "Not found"}},
    dependencies=[],  # add dependencies here
)


async def create_project_class(
    company_url: str = Body(None),
    company_name: str = Body(None),
    team_members: list[str] | None = Body(None),
    industry: str | None = Body(None),
    sector: str | None = Body(None),
    public_status: str | None = Body(None),
    pitch_type: str | None = Body(None),
    targets: list[str] | None = Body(None),
    public_comps: list[str] | None = Body(None),
    project_details: dict | None = Body(None),
    notes: str | None = Body(None),
    project_id: str | None = Body(None),
):
    "Create a project class for the project_id"
    # check if the project is the same as the one in the DB if it exists

    project = Project(
        project_id=project_id,
        company_url=company_url,
        company_name=company_name,
        team_members=team_members,
        industry=industry,
        sector=sector,
        public_status=public_status,
        pitch_type=pitch_type,
        targets=targets,
        public_comps=public_comps,
        project_details=project_details,
        notes=notes,
    )
    return project


ProjectDependency = Annotated[dict, Depends(create_project_class)]


@presentation.post("/generate_ppt/company_profile/")
def generate_company_profile_ppt(company: CompanyProfilePPT):
    "Get recommendations for a company"

    company_url = parsed_url(company.company_url).url
    if company.slide_to_create is None or len(company.slide_to_create) == 0:
        company.slide_to_create = ["private_company_profile"]

    pptg = CompanyProfileSlide(
        company_url=company_url,
        new_deck=True,
    )
    for slide in company.slide_to_create:
        pptg.create_slides(slide)
    if pptg.company_info.stock_ticker is not None:
        pptg.create_slides("stock_chart_output")

    ppt_file_path = "ppt_templates/finalized/%s.pptx" % (
        f"{pptg.company_info.company_name}_company_profile"
    )
    pptg.prs.save(ppt_file_path)

    return FileResponse(ppt_file_path, filename=ppt_file_path.split("/")[-1])


@presentation.post("/get_structure")
async def get_structure(project_id: str, team: Team = Depends(get_team_config_data)):
    "Get the structure of the PPT."

    if not project_id:
        return HTTPException(
            status_code=400, detail={"message": "Project id is not provided"}
        )
    try:
        project = Project.check_project_in_db(project_id=project_id)
        print(team, "team")
        if not project.sections:
            ppt_structure = PPTStructure(project=project, client=team)
            ppt_structure.get_structure()
            return ppt_structure.project
        return project
    except Exception as e:
        XCM_logger.error("Error in get_structure", exc_info=True)
        return HTTPException(status_code=400, detail={"message": str(e)})


@presentation.post("/process_files")
async def process_files(project_id: str, team: Team = Depends(get_team_config_data)):

    "Upload files to the project."
    try:
        # TODO Convert to a task
        doc_uploader_call(project_id=project_id, client=team)
        return {"message": "Files uploaded successfully."}

    except Exception as e:  # pylint: disable=broad-except
        XCM_logger.error(e)
        return HTTPException(status_code=400, detail={"message": str(e)})


@presentation.post("/get_structure/flesh_out_sections")
async def flesh_out_sections(
    project_id: str, team: Team = Depends(get_team_config_data)
):
    "Create the slides from the structure."
    try:
        project = Project.check_project_in_db(project_id=project_id)

        created_ppt_structure = False
        if not project.sections:
            ppt_structure = PPTStructure(project=project, client=team)
            ppt_structure.get_structure()
            ppt_structure.add_or_update_project_in_db()
            project = ppt_structure._update_project()
            created_ppt_structure = True

        if getattr(project.sections[0], "slides", None) in [None, []]:
            if created_ppt_structure is False:
                ppt_structure = PPTStructure(project=project, client=team)
            # ppt_structure = PPTStructure(project=project, client=team)
            await ppt_structure.flesh_out_sections()
            ppt_structure.add_or_update_project_in_db()

            return ppt_structure.project.dict()["sections"]

        return project.dict()["sections"]

    except Exception as e:  # pylint: disable=broad-except
        XCM_logger.error(e)
        return HTTPException(status_code=400, detail={"message": str(e)})


@presentation.post("/get_structure/research_presentation")
def research_presentation(project_id: str, team: Team = Depends(get_team_config_data)):
    "Conduct research for the presentation."
    try:
        project = Project.check_project_in_db(project_id=project_id)
        ppt_structure = PPTStructure(project=project, client=team)
        if not ppt_structure.project.sections:
            ppt_structure.get_structure()
            ppt_structure.add_or_update_project_in_db()
            project = Project.check_project_in_db(project_id=project_id)
            ppt_structure = PPTStructure(project=project, client=team)

        if getattr(ppt_structure.project.sections[0], "slides", None) is None:
            ppt_structure.flesh_out_sections()
            ppt_structure.add_or_update_project_in_db()

        ppt_structure.research_presentation()
        return {"message": "Research conducted successfully."}

    except Exception as e:  # pylint: disable=broad-except
        XCM_logger.error(e)
        return HTTPException(status_code=400, detail={"message": str(e)})


# @presentation.post("/get_structure/research_presentation")
# def research_presentation(project_id: str, team: Team = Depends(get_team_config_data)):
#     "Conduct research for the presentation."
#     try:
#         project = Project.check_project_in_db(project_id=project_id)
#         ppt_structure = PPTStructure(project=project, client=team)
#         if not ppt_structure.project.sections:
#             ppt_structure.get_structure()
#             ppt_structure.add_or_update_project_in_db()
#             project = Project.check_project_in_db(project_id=project_id)
#             ppt_structure = PPTStructure(project=project, client=team)

#         if getattr(ppt_structure.project.sections[0], "slides", None) is None:
#             ppt_structure.flesh_out_sections()
#             ppt_structure.add_or_update_project_in_db()

#         ppt_structure.research_presentation()
#         return {"message": "Research conducted successfully."}

#     except Exception as e: #pylint: disable=broad-except
#         XCM_logger.error(e)
#         return HTTPException(status_code=400, detail={"message": str(e)})


# @presentation.post("/get_structure/research_presentation")
# def research_presentation(project_id: str, team: Team = Depends(get_team_config_data)):
#     "Conduct research for the presentation."
#     try:
#         project = Project.check_project_in_db(project_id=project_id)
#         ppt_structure = PPTStructure(project=project, client=team)
#         if not ppt_structure.project.sections:
#             ppt_structure.get_structure()
#             ppt_structure.add_or_update_project_in_db()
#             project = Project.check_project_in_db(project_id=project_id)
#             ppt_structure = PPTStructure(project=project, client=team)

#         if getattr(ppt_structure.project.sections[0], "slides", None) is None:
#             ppt_structure.flesh_out_sections()
#             ppt_structure.add_or_update_project_in_db()

#         ppt_structure.research_presentation()
#         return {"message": "Research conducted successfully."}

#     except Exception as e:  # pylint: disable=broad-except
#         XCM_logger.error(e)
#         return HTTPException(status_code=400, detail={"message": str(e)})


# @presentation.post("/get_structure/research_presentation")
# def research_presentation(project_id: str, team: Team = Depends(get_team_config_data)):
#     "Conduct research for the presentation."
#     try:
#         project = Project.check_project_in_db(project_id=project_id)
#         ppt_structure = PPTStructure(project=project, client=team)
#         if not ppt_structure.project.sections:
#             ppt_structure.get_structure()
#             ppt_structure.add_or_update_project_in_db()
#             project = Project.check_project_in_db(project_id=project_id)
#             ppt_structure = PPTStructure(project=project, client=team)

#         if getattr(ppt_structure.project.sections[0], "slides", None) is None:
#             ppt_structure.flesh_out_sections()
#             ppt_structure.add_or_update_project_in_db()

#         ppt_structure.research_presentation()
#         return {"message": "Research conducted successfully."}

#     except Exception as e:  # pylint: disable=broad-except
#         XCM_logger.error(e)
#         return HTTPException(status_code=400, detail={"message": str(e)})


@presentation.post("/get_structure/create_ppt")
def create_ppt(project_id: str, team: Team = Depends(get_team_config_data)):
    "Create the PPT from the structure."
    project = Project.check_project_in_db(project_id=project_id)
    ppt_structure = PPTStructure(project=project, client=team)
    deck_location = ppt_structure.create_PPT()
    ppt_structure.add_or_update_project_in_db()

    return FileResponse(deck_location, filename=deck_location.split("/")[-1])


@presentation.post("/update_sections")
async def update_sections(
    project_id: str,
    sections: list[dict] = Body(..., description="Sections to update", embed=True),
    team: Team = Depends(get_team_config_data),
):
    "Update or add new sections to the project structure"
    try:
        project = Project.check_project_in_db(project_id=project_id)
        ppt_structure = PPTStructure(project=project, client=team)

        sectionsLLM = []
        for section in sections:
            section_copy = section.copy()
            slides_data = section_copy.get("slides", []) or []  # Ensure it's a list
            section_copy["slides"] = [Slide(**slide) for slide in slides_data]
            sectionsLLM.append(Section(**section_copy))

        updated_sections = await ppt_structure.flesh_out_new_section(sectionsLLM)

        # Convert sections to JSON-compatible dict using Pydantic
        db_sections = [
            Section.parse_obj(section).dict(exclude_none=True, by_alias=True)
            for section in updated_sections
        ]

        dynamodb = DynamoDB()
        table = dynamodb.get_table("project_structure")
        dynamodb.update_item(
            table=table,
            key={"project_id": project_id},
            update_expression="SET sections = :sections",
            expression_values={":sections": db_sections},
            return_values="ALL_NEW",
        )

        return {
            "message": "Sections updated successfully",
            "sections": updated_sections,
        }

    except Exception as e:
        XCM_logger.error(f"Error updating sections: {str(e)}")
        return HTTPException(status_code=400, detail={"message": str(e)})


@presentation.post("/project")
async def create_project(project: ProjectDependency, team_id: str = Body(None)):
    "Create a project"
    try:
        if not team_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="team_id is required"
            )
        team_service = TeamService()
        existing_team = team_service.get_team_by_id(team_id)
        if not existing_team:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND, detail="Team not found"
            )

        project.company_url = parsed_url(project.company_url).url

        project.client = team_id
        user_service = UserService()

        user_addition_error = []
        # Add project to team
        if project.team_members:
            for user_id in project.team_members:
                user = user_service.get_user_by_id(user_id)
                if user:
                    user.add_project(project.project_id)
                else:
                    user_addition_error.append(user)
        project.update_project_in_db()
        return {"project": project, "users_added_errors": user_addition_error}
    except error_wrappers.ValidationError as e:
        XCM_logger.error(e)
        return HTTPException(status_code=400, detail={"message": str(e)})


@presentation.post("/get_structure/research_presentation/research_slide")
async def research_slide(
    project_id: str,
    section_number: int,
    slide_number: int,
    team: Team = Depends(get_team_config_data),
):
    "Research the slide."
    if not project_id:
        return HTTPException(
            status_code=400, detail={"message": "Project id is not provided"}
        )
    if not section_number:
        return HTTPException(
            status_code=400, detail={"message": "Section number is not provided"}
        )
    if not slide_number:
        return HTTPException(
            status_code=400, detail={"message": "Slide number is not provided"}
        )

    from tasks.presentation_tasks import research_slide as celery_research_slide

    task_id = celery_research_slide.delay(
        project_id, section_number, slide_number, team
    )

    return {"task_id": task_id.id}


@presentation.get("/slide/research")
async def research_slide_status(research_id: str):
    "Check the status of the research slide task."
    db = DynamoDB()
    # research_item = Slide.build_slide_research_id(project_id, section_number, slide_number)
    research_item = db.get_item(db.get_table(db.slides_research_table), research_id)
    return research_item.get("research", "")


@presentation.get("/projects/user")
async def get_user_projects(request: Request):
    "Get user projects"
    try:

        if not request.state.current_user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED, detail="User  not found"
            )

        user_service = UserService()

        user = user_service.get_user_by_id(request.state.current_user.email)
        project_ids = user.projects
        if not project_ids:
            return []

        db = DynamoDB()
        # Prepare keys for batch get
        keys = [{"project_id": pid} for pid in project_ids]

        chunk_size = 100
        projects = []

        for i in range(0, len(keys), chunk_size):
            chunk = keys[i : i + chunk_size]
            response = db.dynamodb.batch_get_item(
                RequestItems={db.projects.name: {"Keys": chunk}}
            )

            if "Responses" in response and db.projects.name in response["Responses"]:
                projects.extend(response["Responses"][db.projects.name])

        ## return a list of project sorted by modified date
        projects.sort(
            key=lambda x: datetime.datetime.fromisoformat(x["modified_date"]),
            reverse=True,
        )
        return projects

    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to get user projects: {str(e)}",
        )


@presentation.post("/upload_provided_files")
async def upload_provided_files(
    files: List[UploadFile] = File(...),
    project_id: str = Body(...),
    team_id: str = Body(...),
):
    "Upload provided files to the project."

    try:

        if not files:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="No files provided for upload",
            )

        team_service = TeamService()
        team = team_service.get_team_by_id(team_id)

        if not team:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND, detail="Team not found"
            )
        project = Project.check_project_in_db(project_id=project_id)

        if not project:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
            )

        try:
            uploadedFiles = DocUploaderV2(project=project, team=team, files=files)
            await uploadedFiles.store_on_s3(files)
            return {
                "message": "Files uploaded successfully",
                "results": uploadedFiles.get_uploaded_files(),
            }
        except Exception as e:
            XCM_logger.error(e)
            raise HTTPException(status_code=400, detail={"message": str(e)}) from e

    except Exception as e:
        XCM_logger.error(e)
        raise HTTPException(status_code=400, detail={"message": str(e)})


@presentation.get("/project/{project_id}")
async def get_project(request: Request, project_id: str, team_id: str = Query(None)):
    "Get a specific project by ID"
    try:
        if not project_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail={"message": "Project ID is required"},
            )

        project = Project.check_project_in_db(project_id=project_id)

        if not project:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail={"message": f"Project with ID {project_id} not found"},
            )

        # Verify team has access to this project
        if project.client != team_id:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail={"message": "You don't have permission to access this project"},
            )

        # Verify user is a team member of the project
        current_user_email = request.state.current_user.email
        team_members = project.team_members or []
        if current_user_email not in team_members:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail={"message": "You are not a member of this project"},
            )

        return {
            "status": status.HTTP_200_OK,
            "data": project.dict(exclude_none=True),
        }

    except HTTPException as he:
        raise he
    except Exception as e:
        XCM_logger.error(f"Error retrieving project: %s", str(e), exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail={"message": "Internal server error while retrieving project"},
        ) from e
