import os
import shutil
import zipfile
from pathlib import Path
from typing import Optional
from urllib.parse import urlparse
from uuid import uuid4

from dotenv import load_dotenv
from fastapi import FastAPI, File, Form, Request, UploadFile
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates

from deploy import deploy, slugify

BASE_DIR = Path(__file__).resolve().parent

load_dotenv(BASE_DIR / ".env")

app = FastAPI(title="Deploy Interno cPanel")

templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))

UPLOAD_DIR = BASE_DIR / "uploads"
WORKDIR = BASE_DIR / "workdir"

UPLOAD_DIR.mkdir(exist_ok=True)
WORKDIR.mkdir(exist_ok=True)

ROOT_DOMAIN = os.getenv("ROOT_DOMAIN", "grupoddm.ia.br")


def extract_slug_from_project_url(project_url: str) -> str:
    project_url = project_url.strip()

    if not project_url:
        raise ValueError("Informe a URL do projeto existente.")

    if not project_url.startswith(("http://", "https://")):
        project_url = "https://" + project_url

    parsed = urlparse(project_url)
    hostname = parsed.hostname

    if not hostname:
        raise ValueError("URL inválida.")

    hostname = hostname.lower()
    suffix = "." + ROOT_DOMAIN.lower()

    if not hostname.endswith(suffix):
        raise ValueError(f"A URL precisa pertencer ao domínio {ROOT_DOMAIN}.")

    slug = hostname[: -len(suffix)]

    if not slug:
        raise ValueError("A URL informada não parece ser um projeto publicado.")

    if "." in slug:
        raise ValueError("Use a URL principal do projeto, sem sub-subdomínios.")

    return slugify(slug)


def safe_extract_zip(zip_path: Path, extract_to: Path) -> None:
    extract_to = extract_to.resolve()

    with zipfile.ZipFile(zip_path, "r") as zip_ref:
        for member in zip_ref.infolist():
            member_path = (extract_to / member.filename).resolve()

            if not str(member_path).startswith(str(extract_to)):
                raise ValueError(
                    "ZIP inválido: tentativa de escrita fora da pasta do projeto."
                )

        zip_ref.extractall(extract_to)


def normalize_extracted_folder(extract_to: Path) -> Path:
    items = [
        item
        for item in extract_to.iterdir()
        if item.name not in ["__MACOSX"]
    ]

    if len(items) == 1 and items[0].is_dir():
        return items[0]

    return extract_to


def base_context(
    result_url: Optional[str] = None,
    error: Optional[str] = None,
    project_name: str = "",
    existing_url: str = "",
    mode: str = "create",
) -> dict:
    return {
        "result_url": result_url,
        "error": error,
        "project_name": project_name,
        "existing_url": existing_url,
        "mode": mode,
    }


@app.get("/", response_class=HTMLResponse)
def home(request: Request):
    return templates.TemplateResponse(
        request,
        "index.html",
        base_context(),
    )


@app.post("/deploy", response_class=HTMLResponse)
async def deploy_project(
    request: Request,
    mode: str = Form("create"),
    project_name: str = Form(""),
    existing_url: str = Form(""),
    file: UploadFile = File(...),
):
    try:
        if mode not in ["create", "update"]:
            raise ValueError("Modo de deploy inválido.")

        if not file.filename:
            raise ValueError("Envie um arquivo .zip.")

        if not file.filename.lower().endswith(".zip"):
            raise ValueError("Por enquanto, envie o projeto em formato .zip.")

        if mode == "create":
            if not project_name.strip():
                raise ValueError("Informe o nome do projeto.")

            deploy_name = project_name.strip()
            slug = slugify(deploy_name)

        else:
            slug = extract_slug_from_project_url(existing_url)
            deploy_name = slug

        job_id = uuid4().hex
        zip_path = UPLOAD_DIR / f"{job_id}-{slug}.zip"
        extract_to = WORKDIR / f"{job_id}-{slug}"

        extract_to.mkdir(parents=True, exist_ok=True)

        with zip_path.open("wb") as buffer:
            shutil.copyfileobj(file.file, buffer)

        safe_extract_zip(zip_path, extract_to)

        project_folder = normalize_extracted_folder(extract_to)

        result_url = deploy(
            project_name=deploy_name,
            source=project_folder,
        )

        return templates.TemplateResponse(
            request,
            "index.html",
            base_context(
                result_url=result_url,
                error=None,
                project_name=project_name,
                existing_url=existing_url,
                mode=mode,
            ),
        )

    except Exception as exc:
        return templates.TemplateResponse(
            request,
            "index.html",
            base_context(
                result_url=None,
                error=str(exc),
                project_name=project_name,
                existing_url=existing_url,
                mode=mode,
            ),
            status_code=400,
        )