"""Materials router — CRUD for the shared material library."""
import uuid
from datetime import datetime
from typing import Optional, Literal

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from sqlalchemy.orm import selectinload
from pydantic import BaseModel

from app.database import get_db
from app.models.material import Material
from app.models.material_alias import MaterialAlias
from app.models.user import User
from app.utils.auth import get_current_user, require_admin_or_pm

router = APIRouter(prefix="/materials", tags=["materials"])


class MaterialOut(BaseModel):
    id: uuid.UUID
    name: str
    description: str | None
    source: str
    schaeffler_code: int | None = None
    created_by_name: str | None = None
    aliases: list[str] = []
    created_at: datetime
    updated_at: datetime
    model_config = {"from_attributes": True}


class MaterialAliasOut(BaseModel):
    id: uuid.UUID
    alias: str
    created_at: datetime
    model_config = {"from_attributes": True}


class MaterialCreate(BaseModel):
    name: str
    description: str | None = None
    source: str = "manual"
    schaeffler_code: int | None = None


class MaterialUpdate(BaseModel):
    name: str | None = None
    description: str | None = None


class AliasCreate(BaseModel):
    alias: str


def _to_out(mat: Material) -> MaterialOut:
    creator_name = None
    if mat.creator is not None:
        creator_name = mat.creator.full_name or mat.creator.email
    alias_names = [a.alias for a in mat.aliases] if mat.aliases else []
    return MaterialOut(
        id=mat.id,
        name=mat.name,
        description=mat.description,
        source=mat.source,
        schaeffler_code=mat.schaeffler_code,
        created_by_name=creator_name,
        aliases=alias_names,
        created_at=mat.created_at,
        updated_at=mat.updated_at,
    )


# --- Static-path endpoints (before /{material_id}) ---


@router.get("/next-code")
async def get_next_code(
    type_prefix: str,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Find the next available consecutive number for a given type+subtype prefix.

    type_prefix is the 4-digit prefix e.g. "0101" for Metals/Steel.
    Returns {"next_code": 10106, "prefix": "0101", "next_consecutive": 6}
    """
    if len(type_prefix) != 4 or not type_prefix.isdigit():
        raise HTTPException(400, "type_prefix must be exactly 4 digits")

    prefix_int = int(type_prefix) * 100  # e.g. "0101" -> 10100
    range_start = prefix_int
    range_end = prefix_int + 99

    result = await db.execute(
        select(func.max(Material.schaeffler_code)).where(
            Material.schaeffler_code >= range_start,
            Material.schaeffler_code <= range_end,
        )
    )
    max_code = result.scalar_one_or_none()

    if max_code is None:
        next_consecutive = 1
    else:
        next_consecutive = (max_code % 100) + 1

    return {
        "next_code": prefix_int + next_consecutive,
        "prefix": type_prefix,
        "next_consecutive": next_consecutive,
    }


@router.post("/seed-schaeffler")
async def seed_schaeffler_materials(
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    """Bulk-create the 35 standard Schaeffler materials. Skips existing by name."""
    from app.data.schaeffler_materials import SCHAEFFLER_MATERIALS

    inserted = 0
    for mat_data in SCHAEFFLER_MATERIALS:
        existing = await db.execute(
            select(Material).where(Material.name == mat_data["name"])
        )
        if existing.scalar_one_or_none():
            continue
        mat = Material(
            name=mat_data["name"],
            description=mat_data["description"],
            source=mat_data["source"],
            schaeffler_code=mat_data["schaeffler_code"],
            created_by=user.id,
        )
        db.add(mat)
        inserted += 1

    await db.commit()
    return {"inserted": inserted, "total": len(SCHAEFFLER_MATERIALS)}


@router.post("/seed-aliases")
async def seed_aliases(
    user: User = Depends(require_admin_or_pm),
    db: AsyncSession = Depends(get_db),
):
    """Bulk-seed aliases from naming_scheme.xlsx Materialmapping data. Skips existing."""
    from app.data.material_alias_seeds import MATERIAL_ALIAS_SEEDS

    inserted = 0
    total = 0
    for entry in MATERIAL_ALIAS_SEEDS:
        mat_result = await db.execute(
            select(Material).where(Material.name == entry["material_name"])
        )
        mat = mat_result.scalar_one_or_none()
        if not mat:
            continue

        for alias_str in entry["aliases"]:
            total += 1
            existing = await db.execute(
                select(MaterialAlias).where(func.lower(MaterialAlias.alias) == alias_str.lower())
            )
            if existing.scalar_one_or_none():
                continue
            db.add(MaterialAlias(material_id=mat.id, alias=alias_str))
            inserted += 1

    await db.commit()
    return {"inserted": inserted, "total": total}


@router.delete("/aliases/{alias_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_alias(
    alias_id: uuid.UUID,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(select(MaterialAlias).where(MaterialAlias.id == alias_id))
    alias_obj = result.scalar_one_or_none()
    if not alias_obj:
        raise HTTPException(404, detail="Alias not found")
    await db.delete(alias_obj)
    await db.commit()


# --- Standard CRUD ---


@router.get("", response_model=list[MaterialOut])
async def list_materials(
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(Material)
        .options(selectinload(Material.creator), selectinload(Material.aliases))
        .order_by(Material.name)
    )
    return [_to_out(m) for m in result.scalars().all()]


@router.post("", response_model=MaterialOut, status_code=status.HTTP_201_CREATED)
async def create_material(
    body: MaterialCreate,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    existing = await db.execute(select(Material).where(Material.name == body.name))
    if existing.scalar_one_or_none():
        raise HTTPException(400, detail=f"Material '{body.name}' already exists")
    mat = Material(
        name=body.name,
        description=body.description,
        source=body.source,
        schaeffler_code=body.schaeffler_code,
        created_by=user.id,
    )
    db.add(mat)
    await db.commit()
    await db.refresh(mat)
    result = await db.execute(
        select(Material)
        .options(selectinload(Material.creator), selectinload(Material.aliases))
        .where(Material.id == mat.id)
    )
    return _to_out(result.scalar_one())


@router.patch("/{material_id}", response_model=MaterialOut)
async def update_material(
    material_id: uuid.UUID,
    body: MaterialUpdate,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(Material)
        .options(selectinload(Material.creator), selectinload(Material.aliases))
        .where(Material.id == material_id)
    )
    mat = result.scalar_one_or_none()
    if not mat:
        raise HTTPException(404, detail="Material not found")
    if body.name is not None:
        mat.name = body.name
    if body.description is not None:
        mat.description = body.description
    mat.updated_at = datetime.utcnow()
    await db.commit()
    await db.refresh(mat)
    result2 = await db.execute(
        select(Material)
        .options(selectinload(Material.creator), selectinload(Material.aliases))
        .where(Material.id == mat.id)
    )
    return _to_out(result2.scalar_one())


@router.delete("/{material_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_material(
    material_id: uuid.UUID,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(select(Material).where(Material.id == material_id))
    mat = result.scalar_one_or_none()
    if not mat:
        raise HTTPException(404, detail="Material not found")
    await db.delete(mat)
    await db.commit()


# --- Alias sub-resource endpoints ---


@router.get("/{material_id}/aliases", response_model=list[MaterialAliasOut])
async def list_aliases(
    material_id: uuid.UUID,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(MaterialAlias)
        .where(MaterialAlias.material_id == material_id)
        .order_by(MaterialAlias.alias)
    )
    return [MaterialAliasOut.model_validate(a) for a in result.scalars().all()]


@router.post("/{material_id}/aliases", response_model=MaterialAliasOut, status_code=status.HTTP_201_CREATED)
async def add_alias(
    material_id: uuid.UUID,
    body: AliasCreate,
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    # Verify material exists
    mat_result = await db.execute(select(Material).where(Material.id == material_id))
    if not mat_result.scalar_one_or_none():
        raise HTTPException(404, detail="Material not found")

    alias_str = body.alias.strip()
    if not alias_str:
        raise HTTPException(400, detail="Alias cannot be empty")

    # Check case-insensitive uniqueness
    existing = await db.execute(
        select(MaterialAlias).where(func.lower(MaterialAlias.alias) == alias_str.lower())
    )
    dup = existing.scalar_one_or_none()
    if dup:
        raise HTTPException(
            status.HTTP_409_CONFLICT,
            detail=f"Alias '{alias_str}' already exists (assigned to material {dup.material_id})",
        )

    alias_obj = MaterialAlias(material_id=material_id, alias=alias_str)
    db.add(alias_obj)
    await db.commit()
    await db.refresh(alias_obj)
    return MaterialAliasOut.model_validate(alias_obj)
