import { and, desc, eq, ilike, or } from "drizzle-orm"
import {
    branches,
    contacts,
    costcentres,
    customers,
    inventoryitems,
    products,
    services,
    teams,
    units,
    vehicles,
    vendors,
} from "../../../db/schema"
import { McpTool } from "../types"

const limitFromArgs = (args: Record<string, unknown>, fallback = 25) => {
    const raw = Number(args.limit ?? fallback)
    if (!Number.isFinite(raw)) return fallback
    return Math.min(Math.max(Math.trunc(raw), 1), 100)
}

const stringArg = (args: Record<string, unknown>, key: string) => {
    const value = args[key]
    return typeof value === "string" && value.trim() ? value.trim() : null
}

const numberArg = (args: Record<string, unknown>, key: string) => {
    const value = Number(args[key])
    return Number.isFinite(value) ? value : null
}

const uuidArg = (args: Record<string, unknown>, key: string) => {
    const value = stringArg(args, key)
    return value && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)
        ? value
        : null
}

export const masterdataTools: McpTool[] = [
    {
        name: "masterdata.customers.get",
        title: "Kunde laden",
        description: "Lädt einen Kunden des aktiven Mandanten anhand seiner ID.",
        requiredPermissions: ["masterdata.customers.read"],
        inputSchema: {
            type: "object",
            required: ["id"],
            properties: { id: { type: "number" } },
        },
        async handler(context, args) {
            const id = numberArg(args, "id")
            if (!id) throw new Error("id ist erforderlich")

            const rows = await context.server.db
                .select()
                .from(customers)
                .where(and(eq(customers.id, id), eq(customers.tenant, context.tenantId)))
                .limit(1)

            if (!rows[0]) throw new Error("Kunde nicht gefunden")
            return { customer: rows[0] }
        },
    },
    {
        name: "masterdata.vendors.search",
        title: "Lieferanten suchen",
        description: "Sucht Lieferanten des aktiven Mandanten.",
        requiredPermissions: ["masterdata.vendors.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Name, Lieferantennummer oder Notizen." },
                includeArchived: { type: "boolean", default: false },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const conditions = [eq(vendors.tenant, context.tenantId)]
            const query = stringArg(args, "query")

            if (query) {
                conditions.push(or(
                    ilike(vendors.name, `%${query}%`),
                    ilike(vendors.vendorNumber, `%${query}%`),
                    ilike(vendors.notes, `%${query}%`)
                ))
            }
            if (args.includeArchived !== true) conditions.push(eq(vendors.archived, false))

            const rows = await context.server.db
                .select()
                .from(vendors)
                .where(and(...conditions))
                .orderBy(vendors.name)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
    {
        name: "masterdata.vendors.get",
        title: "Lieferant laden",
        description: "Lädt einen Lieferanten des aktiven Mandanten anhand seiner ID.",
        requiredPermissions: ["masterdata.vendors.read"],
        inputSchema: {
            type: "object",
            required: ["id"],
            properties: { id: { type: "number" } },
        },
        async handler(context, args) {
            const id = numberArg(args, "id")
            if (!id) throw new Error("id ist erforderlich")

            const rows = await context.server.db
                .select()
                .from(vendors)
                .where(and(eq(vendors.id, id), eq(vendors.tenant, context.tenantId)))
                .limit(1)

            if (!rows[0]) throw new Error("Lieferant nicht gefunden")
            return { vendor: rows[0] }
        },
    },
    {
        name: "masterdata.contacts.search",
        title: "Kontakte suchen",
        description: "Sucht Kontakte des aktiven Mandanten, optional zu Kunde oder Lieferant.",
        requiredPermissions: ["masterdata.contacts.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Name, E-Mail, Telefon, Rolle oder Notizen." },
                customer: { type: "number" },
                vendor: { type: "number" },
                includeArchived: { type: "boolean", default: false },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const conditions = [eq(contacts.tenant, context.tenantId)]
            const query = stringArg(args, "query")
            const customer = numberArg(args, "customer")
            const vendor = numberArg(args, "vendor")

            if (query) {
                conditions.push(or(
                    ilike(contacts.fullName, `%${query}%`),
                    ilike(contacts.firstName, `%${query}%`),
                    ilike(contacts.lastName, `%${query}%`),
                    ilike(contacts.email, `%${query}%`),
                    ilike(contacts.phoneMobile, `%${query}%`),
                    ilike(contacts.phoneHome, `%${query}%`),
                    ilike(contacts.role, `%${query}%`),
                    ilike(contacts.notes, `%${query}%`)
                ))
            }
            if (customer) conditions.push(eq(contacts.customer, customer))
            if (vendor) conditions.push(eq(contacts.vendor, vendor))
            if (args.includeArchived !== true) conditions.push(eq(contacts.archived, false))

            const rows = await context.server.db
                .select()
                .from(contacts)
                .where(and(...conditions))
                .orderBy(contacts.fullName)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
    {
        name: "masterdata.products.search",
        title: "Artikel suchen",
        description: "Sucht Artikel und Materialstammdaten des aktiven Mandanten.",
        requiredPermissions: ["masterdata.products.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Name, Artikelnummer, Hersteller, EAN, Barcode oder Beschreibung." },
                includeArchived: { type: "boolean", default: false },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const conditions = [eq(products.tenant, context.tenantId)]
            const query = stringArg(args, "query")

            if (query) {
                conditions.push(or(
                    ilike(products.name, `%${query}%`),
                    ilike(products.article_number, `%${query}%`),
                    ilike(products.manufacturer, `%${query}%`),
                    ilike(products.manufacturer_number, `%${query}%`),
                    ilike(products.ean, `%${query}%`),
                    ilike(products.barcode, `%${query}%`),
                    ilike(products.description, `%${query}%`)
                ))
            }
            if (args.includeArchived !== true) conditions.push(eq(products.archived, false))

            const rows = await context.server.db
                .select()
                .from(products)
                .where(and(...conditions))
                .orderBy(products.name)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
    {
        name: "masterdata.products.get",
        title: "Artikel laden",
        description: "Lädt einen Artikel des aktiven Mandanten anhand seiner ID.",
        requiredPermissions: ["masterdata.products.read"],
        inputSchema: {
            type: "object",
            required: ["id"],
            properties: { id: { type: "number" } },
        },
        async handler(context, args) {
            const id = numberArg(args, "id")
            if (!id) throw new Error("id ist erforderlich")

            const rows = await context.server.db
                .select()
                .from(products)
                .where(and(eq(products.id, id), eq(products.tenant, context.tenantId)))
                .limit(1)

            if (!rows[0]) throw new Error("Artikel nicht gefunden")
            return { product: rows[0] }
        },
    },
    {
        name: "masterdata.services.search",
        title: "Leistungen suchen",
        description: "Sucht Leistungsstammdaten des aktiven Mandanten.",
        requiredPermissions: ["masterdata.services.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Name, Leistungsnummer oder Beschreibung." },
                includeArchived: { type: "boolean", default: false },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const conditions = [eq(services.tenant, context.tenantId)]
            const query = stringArg(args, "query")

            if (query) {
                conditions.push(or(
                    ilike(services.name, `%${query}%`),
                    ilike(services.description, `%${query}%`)
                ))
            }
            if (args.includeArchived !== true) conditions.push(eq(services.archived, false))

            const rows = await context.server.db
                .select()
                .from(services)
                .where(and(...conditions))
                .orderBy(services.name)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
    {
        name: "masterdata.services.get",
        title: "Leistung laden",
        description: "Lädt eine Leistung des aktiven Mandanten anhand ihrer ID.",
        requiredPermissions: ["masterdata.services.read"],
        inputSchema: {
            type: "object",
            required: ["id"],
            properties: { id: { type: "number" } },
        },
        async handler(context, args) {
            const id = numberArg(args, "id")
            if (!id) throw new Error("id ist erforderlich")

            const rows = await context.server.db
                .select()
                .from(services)
                .where(and(eq(services.id, id), eq(services.tenant, context.tenantId)))
                .limit(1)

            if (!rows[0]) throw new Error("Leistung nicht gefunden")
            return { service: rows[0] }
        },
    },
    {
        name: "masterdata.cost_centres.list",
        title: "Kostenstellen auflisten",
        description: "Listet Kostenstellen des aktiven Mandanten.",
        requiredPermissions: ["masterdata.cost_centres.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Nummer, Name oder Beschreibung." },
                branch: { type: "number" },
                project: { type: "number" },
                includeArchived: { type: "boolean", default: false },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const conditions = [eq(costcentres.tenant, context.tenantId)]
            const query = stringArg(args, "query")
            const branch = numberArg(args, "branch")
            const project = numberArg(args, "project")

            if (query) {
                conditions.push(or(
                    ilike(costcentres.number, `%${query}%`),
                    ilike(costcentres.name, `%${query}%`),
                    ilike(costcentres.description, `%${query}%`)
                ))
            }
            if (branch) conditions.push(eq(costcentres.branch, branch))
            if (project) conditions.push(eq(costcentres.project, project))
            if (args.includeArchived !== true) conditions.push(eq(costcentres.archived, false))

            const rows = await context.server.db
                .select()
                .from(costcentres)
                .where(and(...conditions))
                .orderBy(costcentres.number)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
    {
        name: "masterdata.cost_centres.get",
        title: "Kostenstelle laden",
        description: "Lädt eine Kostenstelle des aktiven Mandanten anhand ihrer UUID.",
        requiredPermissions: ["masterdata.cost_centres.read"],
        inputSchema: {
            type: "object",
            required: ["id"],
            properties: { id: { type: "string" } },
        },
        async handler(context, args) {
            const id = uuidArg(args, "id")
            if (!id) throw new Error("gültige id ist erforderlich")

            const rows = await context.server.db
                .select()
                .from(costcentres)
                .where(and(eq(costcentres.id, id), eq(costcentres.tenant, context.tenantId)))
                .limit(1)

            if (!rows[0]) throw new Error("Kostenstelle nicht gefunden")
            return { costCentre: rows[0] }
        },
    },
    {
        name: "masterdata.branches.list",
        title: "Niederlassungen auflisten",
        description: "Listet Niederlassungen des aktiven Mandanten.",
        requiredPermissions: ["masterdata.branches.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Nummer, Name oder Beschreibung." },
                includeArchived: { type: "boolean", default: false },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const conditions = [eq(branches.tenant, context.tenantId)]
            const query = stringArg(args, "query")

            if (query) {
                conditions.push(or(
                    ilike(branches.number, `%${query}%`),
                    ilike(branches.name, `%${query}%`),
                    ilike(branches.description, `%${query}%`)
                ))
            }
            if (args.includeArchived !== true) conditions.push(eq(branches.archived, false))

            const rows = await context.server.db
                .select()
                .from(branches)
                .where(and(...conditions))
                .orderBy(branches.name)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
    {
        name: "masterdata.teams.list",
        title: "Teams auflisten",
        description: "Listet Teams des aktiven Mandanten.",
        requiredPermissions: ["masterdata.teams.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Name oder Beschreibung." },
                branch: { type: "number" },
                includeArchived: { type: "boolean", default: false },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const conditions = [eq(teams.tenant, context.tenantId)]
            const query = stringArg(args, "query")
            const branch = numberArg(args, "branch")

            if (query) {
                conditions.push(or(
                    ilike(teams.name, `%${query}%`),
                    ilike(teams.description, `%${query}%`)
                ))
            }
            if (branch) conditions.push(eq(teams.branch, branch))
            if (args.includeArchived !== true) conditions.push(eq(teams.archived, false))

            const rows = await context.server.db
                .select()
                .from(teams)
                .where(and(...conditions))
                .orderBy(teams.name)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
    {
        name: "masterdata.vehicles.list",
        title: "Fahrzeuge auflisten",
        description: "Listet Fahrzeuge des aktiven Mandanten.",
        requiredPermissions: ["masterdata.vehicles.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Name, Kennzeichen, FIN oder Farbe." },
                includeArchived: { type: "boolean", default: false },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const conditions = [eq(vehicles.tenant, context.tenantId)]
            const query = stringArg(args, "query")

            if (query) {
                conditions.push(or(
                    ilike(vehicles.name, `%${query}%`),
                    ilike(vehicles.license_plate, `%${query}%`),
                    ilike(vehicles.vin, `%${query}%`),
                    ilike(vehicles.color, `%${query}%`)
                ))
            }
            if (args.includeArchived !== true) conditions.push(eq(vehicles.archived, false))

            const rows = await context.server.db
                .select()
                .from(vehicles)
                .where(and(...conditions))
                .orderBy(vehicles.name)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
    {
        name: "masterdata.inventory_items.search",
        title: "Inventar suchen",
        description: "Sucht Inventar- und Geräte-Stammdaten des aktiven Mandanten.",
        requiredPermissions: ["masterdata.inventory_items.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Name, Artikelnummer, Seriennummer, Hersteller oder Beschreibung." },
                vendor: { type: "number" },
                includeArchived: { type: "boolean", default: false },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const conditions = [eq(inventoryitems.tenant, context.tenantId)]
            const query = stringArg(args, "query")
            const vendor = numberArg(args, "vendor")

            if (query) {
                conditions.push(or(
                    ilike(inventoryitems.name, `%${query}%`),
                    ilike(inventoryitems.articleNumber, `%${query}%`),
                    ilike(inventoryitems.serialNumber, `%${query}%`),
                    ilike(inventoryitems.manufacturer, `%${query}%`),
                    ilike(inventoryitems.manufacturerNumber, `%${query}%`),
                    ilike(inventoryitems.description, `%${query}%`)
                ))
            }
            if (vendor) conditions.push(eq(inventoryitems.vendor, vendor))
            if (args.includeArchived !== true) conditions.push(eq(inventoryitems.archived, false))

            const rows = await context.server.db
                .select()
                .from(inventoryitems)
                .where(and(...conditions))
                .orderBy(inventoryitems.name)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
    {
        name: "masterdata.inventory_items.get",
        title: "Inventar laden",
        description: "Lädt einen Inventar- oder Geräte-Stammdatensatz anhand seiner ID.",
        requiredPermissions: ["masterdata.inventory_items.read"],
        inputSchema: {
            type: "object",
            required: ["id"],
            properties: { id: { type: "number" } },
        },
        async handler(context, args) {
            const id = numberArg(args, "id")
            if (!id) throw new Error("id ist erforderlich")

            const rows = await context.server.db
                .select()
                .from(inventoryitems)
                .where(and(eq(inventoryitems.id, id), eq(inventoryitems.tenant, context.tenantId)))
                .limit(1)

            if (!rows[0]) throw new Error("Inventar nicht gefunden")
            return { inventoryItem: rows[0] }
        },
    },
    {
        name: "masterdata.units.list",
        title: "Einheiten auflisten",
        description: "Listet globale Mengeneinheiten.",
        requiredPermissions: ["masterdata.units.read"],
        inputSchema: {
            type: "object",
            properties: {
                query: { type: "string", description: "Suchtext für Name, Singular, Plural oder Kürzel." },
                limit: { type: "number", minimum: 1, maximum: 100 },
            },
        },
        async handler(context, args) {
            const query = stringArg(args, "query")

            const rows = await context.server.db
                .select()
                .from(units)
                .where(query
                    ? or(
                        ilike(units.name, `%${query}%`),
                        ilike(units.single, `%${query}%`),
                        ilike(units.multiple, `%${query}%`),
                        ilike(units.short, `%${query}%`)
                    )
                    : undefined)
                .orderBy(units.name)
                .limit(limitFromArgs(args))

            return { rows }
        },
    },
]

