package com.ffii.fpsms.modules.master.entity

import com.ffii.core.support.AbstractRepository
import com.ffii.fpsms.modules.master.entity.projections.*
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.Query
import org.springframework.stereotype.Repository
import java.time.LocalDateTime
import java.time.LocalDate

@Repository
interface ProductionScheduleRepository : AbstractRepository<ProductionSchedule, Long> {
    @Query(
        """
        select max(ps.scheduleAt) from ProductionSchedule ps
        where (:type is null or ps.type = :type)
    """
    )
    fun getLatestRoughScheduleAt(type: String?): LocalDateTime

    fun findByTypeAndProduceAtAndDeletedIsFalse(type: String, produceAt: LocalDateTime): ProductionSchedule?

    // Mainly for rough prod schedule page
    @Query(
        nativeQuery = true,
        value =
        """
            with prod as (
	            select
                    ps.id,
                    ps.deleted,
                    ps.scheduleAt,
                    date_add(ps.scheduleAt, interval 7 + 1 - if(weekday(ps.scheduleAt) = 6, 0, weekday(ps.scheduleAt) + 1) day) as schedulePeriod,
                    date_add(ps.scheduleAt, interval 7 + 1 + 6 - if(weekday(ps.scheduleAt) = 6, 0, weekday(ps.scheduleAt) + 1) day) as schedulePeriodTo,
                    ps.totalEstProdCount,
                    ps.totalFGType,
                    ps.`type`
                from production_schedule ps
            ),
            ranked_prod as (
              select
                *,
                row_number() over (partition by date(schedulePeriod) and date(schedulePeriodTo) order by scheduleAt desc) as rn
                from prod
                where deleted = false
            )
            select * from ranked_prod
            where deleted = false
                and rn = 1
                and (:scheduleAt = '' or datediff(scheduleAt, coalesce(:scheduleAt, scheduleAt)) = 0)
                and (:schedulePeriod = '' or datediff(schedulePeriod, coalesce(:schedulePeriod, schedulePeriod)) = 0)
                and (:schedulePeriodTo = '' or datediff(schedulePeriodTo, coalesce(:schedulePeriodTo, schedulePeriodTo)) = 0)
                and (:totalEstProdCount is null or :totalEstProdCount = '' or totalEstProdCount = :totalEstProdCount)
                and (coalesce(:types) is null or type in :types)
            order by id ASC;
        """,
        countQuery =
        """
            with prod as (
	            select
                    ps.id,
                    ps.deleted,
                    ps.scheduleAt,
                    date_add(ps.scheduleAt, interval 7 + 1 - if(weekday(ps.scheduleAt) = 6, 0, weekday(ps.scheduleAt) + 1) day) as schedulePeriod,
                    date_add(ps.scheduleAt, interval 7 + 1 + 6 - if(weekday(ps.scheduleAt) = 6, 0, weekday(ps.scheduleAt) + 1) day) as schedulePeriodTo,
                    ps.totalEstProdCount,
                    ps.totalFGType,
                    ps.`type`
                from production_schedule ps
            ),
            ranked_prod as (
              select
                *,
                row_number() over (partition by date(schedulePeriod) and date(schedulePeriodTo) order by scheduleAt desc) as rn
                from prod
                where deleted = false
            )
            select count(*) from ranked_prod
            where deleted = false
                and rn = 1
                and (:scheduleAt = '' or datediff(scheduleAt, coalesce(:scheduleAt, scheduleAt)) = 0)
                and (:schedulePeriod = '' or datediff(schedulePeriod, coalesce(:schedulePeriod, schedulePeriod)) = 0)
                and (:schedulePeriodTo = '' or datediff(schedulePeriodTo, coalesce(:schedulePeriodTo, schedulePeriodTo)) = 0)
                and (:totalEstProdCount is null or :totalEstProdCount = '' or totalEstProdCount = :totalEstProdCount)
                and (coalesce(:types) is null or type in :types)
            order by id ASC;
        """,
    )
    fun findProdScheduleInfoByPage(
        scheduleAt: String?,
        schedulePeriod: String?,
        schedulePeriodTo: String?,
        totalEstProdCount: Double?,
        types: List<String>?,
        pageable: Pageable
    ): Page<ProdScheduleInfo>

    // Mainly for detailed prod schedule page
    @Query(
        nativeQuery = true,
        value =
        """
            select *
            from (
                select
                    ps.id,
                    ps.deleted,
                    ps.scheduleAt,
                    ps.produceAt,
                    ps.totalEstProdCount,
                    ps.totalFGType,
                    ps.`type`,
                    row_number() over (partition by date(ps.produceAt) order by ps.scheduleAt desc) as rn
                from production_schedule ps
                where deleted = false
            ) ranked_prod
            where rn = 1
                and produceAt is not null
                and scheduleAt in (select max(ps2.scheduleAt) from production_schedule ps2 group by ps2.produceAt)
                AND (
                    :produceAt IS NULL 
                    OR :produceAt = '' 
                    OR Date(produceAt) >= :produceAt
                )
                and (:totalEstProdCount is null or :totalEstProdCount = '' or totalEstProdCount = :totalEstProdCount)
                and (coalesce(:types) is null or type in :types)
            order by id ASC;
        """
    )
    fun findProdScheduleInfoByProduceAtByPage(
        // scheduleAt: String?,
        produceAt: LocalDate,
        totalEstProdCount: Double?,
        types: List<String>?,
        pageable: Pageable
    ): Page<ProdScheduleInfo>

    @Query(nativeQuery = true,
        value =
        """
            with prod_prop as (
                select 
                    psl.id as pslId,
                    -- round(coalesce(psl.prodQty, 0) / coalesce(b.outputQty, 1), 5) as proportion
                    1 as proportion
                from production_schedule ps
                left join production_schedule_line psl on psl.prodScheduleId = ps.id
                left join bom b on b.itemId = psl.itemId
                where ps.id = :id and b.id is not null
            ),
            prod_equip as (
                select
                    r1.pslId,
                    json_arrayagg(json_object('equipName', r1.equipName, 'totalMinutes', r1.totalMinutes)) as prodTimeInMinute
                from (
                    select 
                        psl.id as pslId,
                        coalesce(e.name, 'N/A') as equipName,
                        -- ceil(sum((coalesce(bp.prepTimeInMinute, 0) + coalesce(bp.durationInMinute, 0) + coalesce(bp.postProdTimeInMinute, 0)) * pp.proportion)) as totalMinutes
                        sum(coalesce(bp.prepTimeInMinute, 0) + coalesce(bp.durationInMinute, 0) + coalesce(bp.postProdTimeInMinute, 0)) as totalMinutes
                    from production_schedule ps
                    left join production_schedule_line psl on psl.prodScheduleId = ps.id
                    left join prod_prop pp on pp.pslId = psl.id
                    left join bom b on b.itemId = psl.itemId
                    left join bom_process bp on bp.bomId = b.id
                    left join equipment e on bp.equipmentId = e.id
                    where ps.id = :id and b.id is not null
                    group by psl.id, e.id, pp.proportion
                ) r1
                group by r1.pslId
            ),
            prod_material as (
                select 
                    r2.pslId,
                    r2.bomOutputQty,
                    json_arrayagg(json_object('id', r2.id, 'code', r2.code, 'name', r2.name, 'type', r2.`type`, 'availableQty', r2.availableQty, 'demandQty', r2.demandQty, 'uomName', r2.uomName)) as bomMaterials 
                from (
                    select
                        psl.id as pslId,
                        bm.id,
                        b.outputQty as bomOutputQty,
                        bmi.code,
                        bmi.name,
                        bmi.`type`,
                        coalesce(i.onHandQty, 0) - coalesce(i.onHoldQty, 0) - coalesce(i.unavailableQty, 0) as availableQty,
                        -- ceil(coalesce(bm.qty, 0) * pp.proportion) as demandQty
                        coalesce(bm.qty, 0) * pp.proportion as demandQty,
                        uc.udfudesc as uomName
                    from production_schedule ps
                    left join production_schedule_line psl on psl.prodScheduleId = ps.id
                    left join prod_prop pp on pp.pslId = psl.id
                    left join bom b on b.itemId = psl.itemId 
                    left join bom_material bm on bm.bomId = b.id
                    left join items bmi on bmi.id = bm.itemId
                    left join item_uom iu on iu.itemId = bmi.id and iu.salesUnit = true
                    left join uom_conversion uc on uc.id = iu.uomId
                    left join inventory i on i.itemId = bmi.id
                    where ps.id = :id and bmi.id is not null
                    group by ps.id, psl.id, bm.id, i.id, pp.proportion, uc.udfudesc
                ) r2
                group by r2.pslId, r2.bomOutputQty
            )
            select
                prod.id,
                prod.scheduleAt,
                prod.produceAt,
                prod.totalFGType,
                prod.totalEstProdCount,
                prod.daysLeft,
                prod.needNoOfJobOrder,
                prod.itemPriority,
                prod.batchNeed,
                prod.stockQty,
                prod.outputQty,
                prod.prodQty,
                prod.avgQtyLastMonth,
                
                json_arrayagg(
                    json_object('id', prod.pslId, 
                    'bomMaterials', prod.bomMaterials, 
                    'jobNo', prod.jobNo, 
                    'code', prod.code, 
                    'name', prod.name, 
                    'type', prod.type, 
                    'demandQty', prod.demandQty, 
                    'bomOutputQty', prod.bomOutputQty, 
                    'uomName', prod.uomName, 
                    'prodTimeInMinute', prod.prodTimeInMinute, 
                    'priority', prod.priority, 
                    'approved', prod.approved, 
                    'proportion', prod.proportion, 
                    'daysLeft', prod.daysLeft, 
                    'needNoOfJobOrder', prod.needNoOfJobOrder, 
                    'batchNeed', prod.batchNeed, 
                    'avgQtyLastMonth', prod.avgQtyLastMonth, 
                    'prodQty', prod.prodQty, 
                    'outputQty', prod.outputQty, 
                    'stockQty', prod.stockQty) 
                ) as prodScheduleLines
            from (
                select
                    ps.id,
                    ps.scheduleAt,
                    ps.produceAt,
                    ps.totalFGType,
                    ps.totalEstProdCount,
                    psl.approverId is not null as approved,
                    psl.id as pslId,
                    psl.daysLeft,
                    psl.needNoOfJobOrder,
                    psl.avgQtyLastMonth,
                    psl.itemPriority,
                    psl.batchNeed,
                    psl.stockQty,
                    psl.outputQty,
                    psl.prodQty,
                    pm.bomMaterials,
                    pm.bomOutputQty,
                    uc.udfudesc as uomName,
                    coalesce(jo.code, 'N/A') as jobNo,
                    psli.code,
                    psli.name,
                    psli.`type`,
                    psl.prodQty as demandQty,
                    pe.prodTimeInMinute,
                    psl.itemPriority as priority,
                    pp.proportion
                from production_schedule ps 
                left join production_schedule_line psl on psl.prodScheduleId = ps.id
                left join items psli on psli.id = psl.itemId
                left join item_uom iu on iu.itemId = psli.id and iu.salesUnit = true
                left join uom_conversion uc on uc.id = iu.uomId
                left join job_order jo on jo.prodScheduleLineId = psl.id
                left join prod_prop pp on pp.pslId = psl.id
                left join prod_equip pe on pe.pslId = psl.id
                left join prod_material pm on pm.pslId = psl.id
                where psl.deleted is false and pe.prodTimeInMinute is not null and pm.bomMaterials is not null
                and ps.id = :id
                group by psl.id, jo.id, pp.proportion, pm.bomOutputQty, uc.udfudesc
            ) prod group by prod.id limit 1;
        """
    )
    fun findDetailedProdScheduleWithLine(id: Long): DetailedProdScheduleWithLineWithJsonString?
}