import hashlib
import hmac
import json
import os
import time
import uuid
from datetime import datetime
from typing import List
from pathlib import Path
from urllib.parse import quote
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

import requests
from dotenv import load_dotenv
from fastapi import FastAPI, Request, Form, UploadFile, File, Depends
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
import uvicorn

BASE_DIR = Path(__file__).resolve().parent
load_dotenv(BASE_DIR / '.env')

SMTP_HOST = os.getenv("SMTP_HOST")
SMTP_PORT = int(os.getenv("SMTP_PORT", "465"))
SMTP_USER = os.getenv("SMTP_USER")
SMTP_PASS = os.getenv("SMTP_PASS")
FROM_NAME = os.getenv("FROM_NAME", "Tumbo & Son's Contractors Ltd")
FROM_EMAIL = os.getenv("FROM_EMAIL")
TO_EMAIL = os.getenv("TO_EMAIL")

TEXTSMS_CONFIG = {
    'API_KEY': os.getenv('TEXTSMS_API_KEY'),
    'PARTNER_ID': os.getenv('TEXTSMS_PARTNER_ID'),
    'SENDER_ID': os.getenv('TEXTSMS_SENDER_ID', 'TextSMS'),
    'SHORTCODE': os.getenv('TEXTSMS_SHORTCODE'),
    'ENABLED': os.getenv('TEXTSMS_ENABLED', 'True').lower() == 'true',
}
SMS_ADMIN_PHONE = os.getenv("SMS_ADMIN_PHONE", "")

ADMIN_EMAIL = os.getenv("ADMIN_EMAIL", "admin@example.com")
ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "admin123")
SECRET_KEY = os.getenv("SECRET_KEY", "default-secret")
SITE_URL = os.getenv("SITE_URL", "").rstrip("/")

PROJECTS_FILE = BASE_DIR / "data" / "projects.json"
UPLOAD_DIR = BASE_DIR / "static" / "uploads"
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)

app = FastAPI(title="TUMBO AND SON'S CONTRACTOR'S LTD")

templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
templates.env.globals["site_url"] = SITE_URL

def url_quote_filter(value):
    return quote(str(value), safe="")

templates.env.filters["url_quote"] = url_quote_filter

app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")


def render(template_name: str, context: dict) -> HTMLResponse:
    template = templates.get_template(template_name)
    html = template.render(context)
    return HTMLResponse(content=html)


def sign_token(data: str) -> str:
    return hmac.new(SECRET_KEY.encode(), data.encode(), hashlib.sha256).hexdigest()[:16]


def create_session(email: str) -> str:
    ts = str(int(time.time()))
    payload = f"{email}:{ts}"
    return f"{payload}:{sign_token(payload)}"


def verify_session(token: str) -> bool:
    try:
        payload, sig = token.rsplit(":", 1)
        return hmac.compare_digest(sig, sign_token(payload))
    except Exception:
        return False


def get_admin_from_request(request: Request) -> str | None:
    token = request.cookies.get("admin_session")
    if not token:
        return None
    if not verify_session(token):
        return None
    try:
        email = token.rsplit(":", 1)[0].rsplit(":", 1)[0]
        return email
    except Exception:
        return None


def require_admin(request: Request):
    email = get_admin_from_request(request)
    if not email:
        return RedirectResponse(url="/admin/login", status_code=302)
    return email


def read_projects() -> list[dict]:
    if not PROJECTS_FILE.exists():
        return []
    try:
        with open(PROJECTS_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return []


def save_projects(projects: list[dict]):
    PROJECTS_FILE.parent.mkdir(parents=True, exist_ok=True)
    with open(PROJECTS_FILE, "w", encoding="utf-8") as f:
        json.dump(projects, f, indent=2, ensure_ascii=False)


def send_contact_email(name: str, phone: str, email: str, message: str):
    try:
        msg = MIMEMultipart()
        msg['Subject'] = "New Project Enquiry — TUMBO AND SON'S CONTRACTOR'S LTD"
        msg['From'] = f"{FROM_NAME} <{FROM_EMAIL}>"
        msg['To'] = TO_EMAIL

        body = (
            "A new project enquiry has been submitted through the website.\n\n"
            f"Name: {name}\n"
            f"Phone: {phone}\n"
            f"Email: {email}\n"
            f"Message:\n{message}\n"
        )
        msg.attach(MIMEText(body, 'plain'))

        server = smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT)
        server.login(SMTP_USER, SMTP_PASS)
        server.send_message(msg)
        server.quit()
        return True
    except Exception as e:
        print(f"Failed to send email: {e}")
        return False


def format_phone(phone: str) -> str:
    phone = phone.strip().replace('+', '').replace(' ', '')
    if phone.startswith('254'):
        return phone
    if phone.startswith('0'):
        return '254' + phone[1:]
    if phone.startswith(('7', '1')):
        return '254' + phone
    return '254' + phone


def send_sms_textsms(phone: str, message: str):
    try:
        phone = format_phone(phone)
        shortcode = TEXTSMS_CONFIG['SHORTCODE'] or TEXTSMS_CONFIG['SENDER_ID']
        payload = {
            "apikey": TEXTSMS_CONFIG['API_KEY'],
            "partnerID": TEXTSMS_CONFIG['PARTNER_ID'],
            "message": message,
            "shortcode": shortcode,
            "mobile": phone,
            "pass_type": "plain"
        }
        headers = {'Content-Type': 'application/json'}
        response = requests.post(
            'https://sms.textsms.co.ke/api/services/sendsms/',
            json=payload,
            headers=headers,
            timeout=30
        )
        if response.status_code == 200:
            result = response.json()
            if 'responses' in result and len(result['responses']) > 0:
                first_response = result['responses'][0]
                response_code = first_response.get('response-code')
                if response_code == 200:
                    return {'success': True}
                else:
                    return {'success': False, 'error': first_response.get('response-description', 'Unknown error')}
            return {'success': False, 'error': 'Invalid response format'}
        return {'success': False, 'error': f'HTTP {response.status_code}'}
    except Exception as e:
        print(f"Failed to send SMS: {e}")
        return {'success': False, 'error': str(e)}


@app.get("/", response_class=HTMLResponse)
async def home(request: Request):
    projects = read_projects()
    featured = [p for p in projects if p.get("is_featured")]
    others = [p for p in projects if not p.get("is_featured")]
    display = (featured + others)[:6]
    return render("index.html", {
        "request": request,
        "active_page": "home",
        "projects": display
    })


@app.get("/services", response_class=HTMLResponse)
async def services(request: Request):
    return render("services.html", {"request": request, "active_page": "services"})


@app.get("/portfolio", response_class=HTMLResponse)
async def portfolio(request: Request):
    projects = read_projects()
    return render("portfolio.html", {
        "request": request,
        "active_page": "portfolio",
        "projects": projects
    })


@app.get("/blog", response_class=HTMLResponse)
async def blog(request: Request):
    return render("blog.html", {"request": request, "active_page": "blog"})


@app.get("/blog/how-to-plan-your-construction-budget", response_class=HTMLResponse)
async def blog_budget_guide(request: Request):
    return render("blog_budget_guide.html", {"request": request, "active_page": "blog"})


@app.get("/contact", response_class=HTMLResponse)
async def contact(request: Request):
    return render("contact.html", {"request": request, "active_page": "contact"})


@app.post("/contact", response_class=HTMLResponse)
async def contact_submit(
    request: Request,
    name: str = Form(""),
    email: str = Form(""),
    phone: str = Form(""),
    message: str = Form("")
):
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    submission = (
        f"[{timestamp}]\n"
        f"Name: {name}\n"
        f"Phone: {phone}\n"
        f"Email: {email}\n"
        f"Message: {message}\n"
        f"{'-' * 40}\n"
    )
    with open(BASE_DIR / "enquiries.txt", "a", encoding="utf-8") as f:
        f.write(submission)

    email_sent = send_contact_email(name, phone, email, message)

    sms_name = name or "A customer"
    sms_message = (
        f"Project enquiry: {sms_name} contacted you. "
        f"Phone: {phone or 'N/A'}. Check your email for details."
    )
    if TEXTSMS_CONFIG['ENABLED'] and SMS_ADMIN_PHONE:
        send_sms_textsms(SMS_ADMIN_PHONE, sms_message)

    return render("contact.html", {
        "request": request,
        "active_page": "contact",
        "submitted": True,
        "email_sent": email_sent
    })


@app.get("/admin/login", response_class=HTMLResponse)
async def admin_login_page(request: Request):
    return render("admin_login.html", {"request": request, "error": None})


@app.post("/admin/login")
async def admin_login_post(request: Request, email: str = Form(""), password: str = Form("")):
    if email == ADMIN_EMAIL and hmac.compare_digest(password, ADMIN_PASSWORD):
        resp = RedirectResponse(url="/admin", status_code=302)
        resp.set_cookie("admin_session", create_session(email), httponly=True, samesite="lax")
        return resp
    return render("admin_login.html", {"request": request, "error": "Invalid email or password."})


@app.get("/admin/logout")
async def admin_logout():
    resp = RedirectResponse(url="/admin/login", status_code=302)
    resp.delete_cookie("admin_session")
    return resp


@app.get("/admin", response_class=HTMLResponse)
async def admin_dashboard(request: Request):
    admin_email = require_admin(request)
    if isinstance(admin_email, RedirectResponse):
        return admin_email
    projects = read_projects()
    return render("admin_dashboard.html", {
        "request": request,
        "projects": projects,
        "admin_email": admin_email
    })


@app.post("/admin/projects")
async def admin_add_project(
    request: Request,
    name: str = Form(""),
    description: str = Form(""),
    location: str = Form(""),
    map_url: str = Form(""),
    status: str = Form("ongoing"),
    budget: str = Form(""),
    area_size: str = Form(""),
    services: List[str] = Form([]),
    video_urls: str = Form(""),
    is_featured: str = Form(""),
    image: UploadFile = File(None),
    additional_images: List[UploadFile] = File(None)
):
    admin_email = require_admin(request)
    if isinstance(admin_email, RedirectResponse):
        return admin_email

    projects = read_projects()

    if not name.strip() or not location.strip() or not (image and image.filename):
        return render("admin_dashboard.html", {
            "request": request,
            "projects": projects,
            "admin_email": admin_email,
            "error": "Project name, location, and a main image are required."
        })

    proj_id = f"proj-{uuid.uuid4().hex[:8]}"
    image_path = ""

    if image and image.filename:
        ext = Path(image.filename).suffix.lower()
        if ext not in (".jpg", ".jpeg", ".png", ".webp", ".gif"):
            return render("admin_dashboard.html", {
                "request": request,
                "projects": projects,
                "admin_email": admin_email,
                "error": "Invalid main image format. Use JPG, PNG, WEBP or GIF."
            })
        filename = f"{proj_id}{ext}"
        filepath = UPLOAD_DIR / filename
        with open(filepath, "wb") as f:
            f.write(await image.read())
        image_path = f"uploads/{filename}"

    extra_images = []
    files = additional_images if additional_images else []
    if len(files) > 30:
        return render("admin_dashboard.html", {
            "request": request,
            "projects": projects,
            "admin_email": admin_email,
            "error": "Maximum 30 additional images allowed."
        })

    for idx, img in enumerate(files):
        if img and img.filename:
            ext = Path(img.filename).suffix.lower()
            if ext not in (".jpg", ".jpeg", ".png", ".webp", ".gif"):
                continue
            filename = f"{proj_id}-extra-{idx}{ext}"
            filepath = UPLOAD_DIR / filename
            with open(filepath, "wb") as f:
                f.write(await img.read())
            extra_images.append(f"uploads/{filename}")

    videos = [v.strip() for v in video_urls.splitlines() if v.strip()]

    new_project = {
        "id": proj_id,
        "name": name,
        "description": description,
        "location": location,
        "map_url": map_url,
        "status": status,
        "budget": budget,
        "area_size": area_size,
        "services": services,
        "video_urls": videos,
        "is_featured": is_featured == "on",
        "image": image_path,
        "additional_images": extra_images,
        "created_at": datetime.now().isoformat()
    }
    projects.append(new_project)
    save_projects(projects)

    return RedirectResponse(url="/admin", status_code=302)


@app.get("/admin/projects/{project_id}/edit", response_class=HTMLResponse)
async def admin_edit_project_form(request: Request, project_id: str):
    admin_email = require_admin(request)
    if isinstance(admin_email, RedirectResponse):
        return admin_email
    projects = read_projects()
    project = next((p for p in projects if p.get("id") == project_id), None)
    if not project:
        return RedirectResponse(url="/admin", status_code=302)
    return render("admin_edit_project.html", {
        "request": request,
        "project": project,
        "admin_email": admin_email
    })


@app.post("/admin/projects/{project_id}/edit")
async def admin_edit_project(
    request: Request,
    project_id: str,
    name: str = Form(""),
    description: str = Form(""),
    location: str = Form(""),
    map_url: str = Form(""),
    status: str = Form("ongoing"),
    budget: str = Form(""),
    area_size: str = Form(""),
    services: List[str] = Form([]),
    video_urls: str = Form(""),
    is_featured: str = Form(""),
    image: UploadFile = File(None),
    delete_images: List[str] = Form([]),
    additional_images: List[UploadFile] = File(None)
):
    admin_email = require_admin(request)
    if isinstance(admin_email, RedirectResponse):
        return admin_email

    projects = read_projects()
    project = next((p for p in projects if p.get("id") == project_id), None)
    if not project:
        return RedirectResponse(url="/admin", status_code=302)

    if not name.strip() or not location.strip():
        return render("admin_edit_project.html", {
            "request": request,
            "project": project,
            "admin_email": admin_email,
            "error": "Project name and location are required."
        })

    project["name"] = name
    project["description"] = description
    project["location"] = location
    project["map_url"] = map_url
    project["status"] = status
    project["budget"] = budget
    project["area_size"] = area_size
    project["services"] = services
    project["video_urls"] = [v.strip() for v in video_urls.splitlines() if v.strip()]
    project["is_featured"] = is_featured == "on"

    if image and image.filename:
        ext = Path(image.filename).suffix.lower()
        if ext not in (".jpg", ".jpeg", ".png", ".webp", ".gif"):
            return render("admin_edit_project.html", {
                "request": request,
                "project": project,
                "admin_email": admin_email,
                "error": "Invalid main image format. Use JPG, PNG, WEBP or GIF."
            })
        old_img = project.get("image", "")
        if old_img:
            try:
                (BASE_DIR / "static" / old_img).unlink(missing_ok=True)
            except Exception:
                pass
        filename = f"{project_id}{ext}"
        filepath = UPLOAD_DIR / filename
        with open(filepath, "wb") as f:
            f.write(await image.read())
        project["image"] = f"uploads/{filename}"

    current_extra = project.get("additional_images", [])
    to_delete = delete_images if delete_images else []
    for img_path in to_delete:
        if img_path in current_extra:
            try:
                (BASE_DIR / "static" / img_path).unlink(missing_ok=True)
            except Exception:
                pass
            current_extra.remove(img_path)

    files = additional_images if additional_images else []
    valid_new = [f for f in files if f and f.filename]
    if len(current_extra) + len(valid_new) > 30:
        return render("admin_edit_project.html", {
            "request": request,
            "project": project,
            "admin_email": admin_email,
            "error": f"Maximum 30 additional images allowed. You currently have {len(current_extra)}."
        })

    for img in valid_new:
        ext = Path(img.filename).suffix.lower()
        if ext not in (".jpg", ".jpeg", ".png", ".webp", ".gif"):
            continue
        filename = f"{project_id}-extra-{len(current_extra)}{ext}"
        filepath = UPLOAD_DIR / filename
        with open(filepath, "wb") as f:
            f.write(await img.read())
        current_extra.append(f"uploads/{filename}")

    project["additional_images"] = current_extra
    save_projects(projects)
    return RedirectResponse(url="/admin", status_code=302)


@app.get("/portfolio/{project_id}", response_class=HTMLResponse)
async def project_detail(request: Request, project_id: str):
    projects = read_projects()
    project = next((p for p in projects if p.get("id") == project_id), None)
    if not project:
        return RedirectResponse(url="/portfolio", status_code=302)
    return render("project_detail.html", {
        "request": request,
        "project": project,
        "active_page": "portfolio"
    })


@app.post("/admin/projects/{project_id}/delete")
async def admin_delete_project(request: Request, project_id: str):
    admin_email = require_admin(request)
    if isinstance(admin_email, RedirectResponse):
        return admin_email

    projects = read_projects()
    target = next((p for p in projects if p.get("id") == project_id), None)
    if target:
        for img in [target.get("image", "")] + target.get("additional_images", []):
            if img:
                try:
                    (BASE_DIR / "static" / img).unlink(missing_ok=True)
                except Exception:
                    pass
    projects = [p for p in projects if p.get("id") != project_id]
    save_projects(projects)
    return RedirectResponse(url="/admin", status_code=302)


if __name__ == "__main__":
    uvicorn.run("application:app", host="0.0.0.0", port=8000)
