from fastapi import FastAPI, Depends, HTTPException, APIRouter
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from sqlalchemy.orm import Session
from sqlalchemy import func
from typing import List, Optional
from datetime import datetime, date, timedelta
import json
import os

import models, schemas, database
from database import SessionLocal, engine, get_db

models.Base.metadata.create_all(bind=engine)

app = FastAPI(title="DealBoard")
router = APIRouter(prefix="/api")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

CLIENT_PALETTE = [
    "#ef4444", "#f97316", "#f59e0b", "#84cc16", "#10b981",
    "#06b6d4", "#3b82f6", "#6366f1", "#8b5cf6", "#d946ef",
    "#f43f5e", "#78716c",
]


def get_next_client_color(db: Session) -> str:
    used = {c.color for c in db.query(models.Client).all()}
    for color in CLIENT_PALETTE:
        if color not in used:
            return color
    return CLIENT_PALETTE[len(used) % len(CLIENT_PALETTE)]


def get_deal_shade(client_id: int, db: Session) -> int:
    max_shade = (
        db.query(func.max(models.Deal.shade_index))
        .filter(models.Deal.client_id == client_id, models.Deal.status == "active")
        .scalar()
    )
    return (max_shade or -1) + 1


def seed_columns(db: Session):
    if db.query(models.BoardColumn).first() is None:
        defaults = [
            ("Do Myself", 0),
            ("Check Assignee", 1),
            ("External Follow-Up", 2),
            ("Admin", 3),
        ]
        for name, order in defaults:
            db.add(models.BoardColumn(name=name, sort_order=order))
        db.commit()


@app.on_event("startup")
def on_startup():
    db = SessionLocal()
    seed_columns(db)
    db.close()


# Clients
@router.post("/clients", response_model=schemas.ClientOut)
def create_client(client: schemas.ClientCreate, db: Session = Depends(get_db)):
    existing = db.query(models.Client).filter(models.Client.name == client.name).first()
    if existing:
        return existing
    color = get_next_client_color(db)
    db_client = models.Client(name=client.name, color=color)
    db.add(db_client)
    db.commit()
    db.refresh(db_client)
    return db_client


@router.get("/clients", response_model=List[schemas.ClientOut])
def list_clients(db: Session = Depends(get_db)):
    return db.query(models.Client).order_by(models.Client.name).all()


# Deals
@router.post("/deals", response_model=schemas.DealOut)
def create_deal(deal: schemas.DealCreate, db: Session = Depends(get_db)):
    shade = get_deal_shade(deal.client_id, db)
    db_deal = models.Deal(
        name=deal.name,
        client_id=deal.client_id,
        shade_index=shade,
        status=deal.status,
    )
    db.add(db_deal)
    db.commit()
    db.refresh(db_deal)
    return db_deal


@router.get("/deals", response_model=List[schemas.DealOut])
def list_deals(client_id: Optional[int] = None, status: Optional[str] = None, db: Session = Depends(get_db)):
    q = db.query(models.Deal)
    if client_id is not None:
        q = q.filter(models.Deal.client_id == client_id)
    if status is not None:
        q = q.filter(models.Deal.status == status)
    return q.order_by(models.Deal.name).all()


@router.patch("/deals/{deal_id}", response_model=schemas.DealOut)
def update_deal(deal_id: int, status: Optional[str] = None, db: Session = Depends(get_db)):
    deal = db.query(models.Deal).filter(models.Deal.id == deal_id).first()
    if not deal:
        raise HTTPException(status_code=404, detail="Deal not found")
    if status is not None:
        deal.status = status
    db.commit()
    db.refresh(deal)
    return deal


# Columns
@router.get("/columns", response_model=List[schemas.ColumnOut])
def list_columns(db: Session = Depends(get_db)):
    return db.query(models.BoardColumn).order_by(models.BoardColumn.sort_order).all()


@router.post("/columns", response_model=schemas.ColumnOut)
def create_column(col: schemas.ColumnCreate, db: Session = Depends(get_db)):
    db_col = models.BoardColumn(name=col.name, sort_order=col.sort_order)
    db.add(db_col)
    db.commit()
    db.refresh(db_col)
    return db_col


@router.patch("/columns/{col_id}", response_model=schemas.ColumnOut)
def update_column(col_id: int, col: schemas.ColumnCreate, db: Session = Depends(get_db)):
    db_col = db.query(models.BoardColumn).filter(models.BoardColumn.id == col_id).first()
    if not db_col:
        raise HTTPException(status_code=404, detail="Column not found")
    db_col.name = col.name
    db_col.sort_order = col.sort_order
    db.commit()
    db.refresh(db_col)
    return db_col


# Tasks
@router.post("/tasks", response_model=schemas.TaskOut)
def create_task(task: schemas.TaskCreate, db: Session = Depends(get_db)):
    now = datetime.utcnow().isoformat()
    # auto-priorities: append to end
    max_master = db.query(func.max(models.Task.master_priority)).scalar() or 0.0
    max_col = (
        db.query(func.max(models.Task.column_priority))
        .filter(models.Task.column_id == task.column_id)
        .scalar()
        or 0.0
    )
    task_data = task.dict(exclude={'master_priority', 'column_priority'})
    db_task = models.Task(
        **task_data,
        created_at=now,
        master_priority=max_master + 1.0,
        column_priority=max_col + 1.0,
    )
    db.add(db_task)
    db.commit()
    db.refresh(db_task)
    return db_task


@router.get("/tasks", response_model=List[schemas.TaskOut])
def list_tasks(
    client_id: Optional[int] = None,
    deal_id: Optional[int] = None,
    column_id: Optional[int] = None,
    is_quick: Optional[bool] = None,
    completed: Optional[bool] = False,
    db: Session = Depends(get_db),
):
    q = db.query(models.Task)
    if completed is False:
        q = q.filter(models.Task.completed_at.is_(None))
    elif completed is True:
        q = q.filter(models.Task.completed_at.isnot(None))
    if client_id is not None:
        q = q.filter(models.Task.client_id == client_id)
    if deal_id is not None:
        q = q.filter(models.Task.deal_id == deal_id)
    if column_id is not None:
        q = q.filter(models.Task.column_id == column_id)
    if is_quick is not None:
        q = q.filter(models.Task.is_quick == is_quick)
    return q.all()


@router.patch("/tasks/{task_id}", response_model=schemas.TaskOut)
def update_task(task_id: int, task: schemas.TaskUpdate, db: Session = Depends(get_db)):
    db_task = db.query(models.Task).filter(models.Task.id == task_id).first()
    if not db_task:
        raise HTTPException(status_code=404, detail="Task not found")
    for field, value in task.dict(exclude_unset=True).items():
        setattr(db_task, field, value)
    db.commit()
    db.refresh(db_task)
    return db_task


@router.delete("/tasks/{task_id}")
def delete_task(task_id: int, db: Session = Depends(get_db)):
    db_task = db.query(models.Task).filter(models.Task.id == task_id).first()
    if not db_task:
        raise HTTPException(status_code=404, detail="Task not found")
    db.delete(db_task)
    db.commit()
    return {"ok": True}


# Bulk reorder
@router.post("/tasks/reorder")
def reorder_tasks(updates: List[dict], db: Session = Depends(get_db)):
    for u in updates:
        t = db.query(models.Task).filter(models.Task.id == u["id"]).first()
        if t:
            if "master_priority" in u:
                t.master_priority = u["master_priority"]
            if "column_priority" in u:
                t.column_priority = u["column_priority"]
            if "column_id" in u:
                t.column_id = u["column_id"]
    db.commit()
    return {"ok": True}


# Completed log
@router.get("/completed", response_model=List[schemas.TaskOut])
def completed_log(limit: int = 200, db: Session = Depends(get_db)):
    return (
        db.query(models.Task)
        .filter(models.Task.completed_at.isnot(None))
        .order_by(models.Task.completed_at.desc())
        .limit(limit)
        .all()
    )


# Templates
@router.post("/templates", response_model=schemas.TemplateOut)
def create_template(tpl: schemas.TemplateCreate, db: Session = Depends(get_db)):
    db_tpl = models.Template(name=tpl.name, items=json.dumps([i.dict() for i in tpl.items]))
    db.add(db_tpl)
    db.commit()
    db.refresh(db_tpl)
    db_tpl.items = json.loads(db_tpl.items)
    return db_tpl


@router.get("/templates", response_model=List[schemas.TemplateOut])
def list_templates(db: Session = Depends(get_db)):
    rows = db.query(models.Template).all()
    for r in rows:
        r.items = json.loads(r.items)
    return rows


@router.post("/templates/{tpl_id}/spawn")
def spawn_template(tpl_id: int, client_id: int, deal_id: int, db: Session = Depends(get_db)):
    tpl = db.query(models.Template).filter(models.Template.id == tpl_id).first()
    if not tpl:
        raise HTTPException(status_code=404, detail="Template not found")
    items = json.loads(tpl.items)
    now = datetime.utcnow().isoformat()
    max_master = db.query(func.max(models.Task.master_priority)).scalar() or 0.0
    max_col = (
        db.query(func.max(models.Task.column_priority))
        .filter(models.Task.column_id.in_([i.get("column_id") for i in items if i.get("column_id")]))
        .scalar()
        or 0.0
    )
    created = []
    for idx, item in enumerate(items):
        db_task = models.Task(
            title=item["title"],
            client_id=client_id,
            deal_id=deal_id,
            column_id=item.get("column_id"),
            is_quick=item.get("is_quick", False),
            created_at=now,
            master_priority=max_master + idx + 1.0,
            column_priority=max_col + idx + 1.0,
        )
        db.add(db_task)
        db.commit()
        db.refresh(db_task)
        created.append(db_task)
    return created


def hsl_to_hex(h, s, l):
    """Convert HSL to hex color."""
    s /= 100.0
    l /= 100.0
    c = (1 - abs(2 * l - 1)) * s
    x = c * (1 - abs((h / 60) % 2 - 1))
    m = l - c / 2
    if h < 60: r, g, b = c, x, 0
    elif h < 120: r, g, b = x, c, 0
    elif h < 180: r, g, b = 0, c, x
    elif h < 240: r, g, b = 0, x, c
    elif h < 300: r, g, b = x, 0, c
    else: r, g, b = c, 0, x
    return "#{:02x}{:02x}{:02x}".format(int((r + m) * 255), int((g + m) * 255), int((b + m) * 255))


@router.post("/import")
def import_data(payload: dict, db: Session = Depends(get_db)):
    """Import data from HTML app JSON export."""
    clients_data = payload.get("clients", [])
    deals_data = payload.get("deals", [])
    tasks_data = payload.get("tasks", [])

    # Clear existing data
    db.query(models.Task).delete()
    db.query(models.Deal).delete()
    db.query(models.Client).delete()
    db.commit()

    now = datetime.utcnow()

    # Create clients, mapping old string IDs to new integer IDs
    client_map = {}
    for c in clients_data:
        color = hsl_to_hex(c.get("hue", 0), 65, 32)
        db_client = models.Client(name=c["name"], color=color)
        db.add(db_client)
        db.commit()
        db.refresh(db_client)
        client_map[c["id"]] = db_client.id

    # Create deals
    deal_map = {}
    for d in deals_data:
        client_id = client_map.get(d.get("clientId"))
        if not client_id:
            continue
        shade = get_deal_shade(client_id, db)
        db_deal = models.Deal(name=d["name"], client_id=client_id, shade_index=shade, status="active")
        db.add(db_deal)
        db.commit()
        db.refresh(db_deal)
        deal_map[d["id"]] = db_deal.id

    # Get first column for default assignment
    first_col = db.query(models.BoardColumn).order_by(models.BoardColumn.sort_order).first()
    col_id = first_col.id if first_col else None

    # Create tasks
    for idx, t in enumerate(tasks_data):
        deal_id = deal_map.get(t.get("dealId")) if t.get("dealId") else None
        client_id = None
        if deal_id:
            deal = db.query(models.Deal).filter(models.Deal.id == deal_id).first()
            if deal:
                client_id = deal.client_id

        due_offset = t.get("due")
        due_date = None
        if due_offset is not None and due_offset != "":
            due_date = (now + timedelta(days=int(due_offset))).isoformat()

        db_task = models.Task(
            title=t["title"],
            client_id=client_id,
            deal_id=deal_id,
            column_id=col_id,
            due_date=due_date,
            is_quick=False,
            assignee=t.get("team") or None,
            waiting_on=t.get("external") or None,
            completed_at=now.isoformat() if t.get("done") and t.get("doneAt") else None,
            master_priority=float(idx + 1),
            column_priority=float(idx + 1),
            created_at=now.isoformat(),
        )
        db.add(db_task)

    db.commit()
    return {"imported": {"clients": len(client_map), "deals": len(deal_map), "tasks": len(tasks_data)}}


# State snapshot for HTML app cloud sync
@router.get("/state")
def get_state(db: Session = Depends(get_db)):
    snap = db.query(models.StateSnapshot).filter(models.StateSnapshot.key == "default").first()
    if not snap or not snap.data:
        raise HTTPException(status_code=404, detail="No state found")
    return json.loads(snap.data)

@router.post("/state")
def post_state(payload: dict, db: Session = Depends(get_db)):
    now = datetime.utcnow().isoformat()
    snap = db.query(models.StateSnapshot).filter(models.StateSnapshot.key == "default").first()
    if snap:
        snap.data = json.dumps(payload)
        snap.updated_at = now
    else:
        snap = models.StateSnapshot(key="default", data=json.dumps(payload), updated_at=now)
        db.add(snap)
    db.commit()
    return {"ok": True, "updated_at": now}


app.include_router(router)

# React app at /app/
react_dir = os.path.join(os.path.dirname(__file__), "..", "static", "app")
if os.path.exists(react_dir):
    app.mount("/app", StaticFiles(directory=react_dir, html=True), name="react")

# HTML app at /
static_dir = os.path.join(os.path.dirname(__file__), "..", "static")
if os.path.exists(static_dir):
    app.mount("/", StaticFiles(directory=static_dir, html=True), name="static")
