package com.zioinfo.kintex.auction;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Update;

import java.util.List;
import java.util.Map;

/**
 * M15 옥션 매퍼 — auction·auction_invite·bid·award·company_reputation.
 * <p><b>★ Map 반환 @Select 별칭은 반드시 쌍따옴표(AS "x")</b> — PG는 따옴표 없는 별칭을 소문자로 접어
 * Map 키가 전부 null이 된다(WORK_STATUS §7). 신규 매퍼 필수 점검.
 * <p>봉인 입찰 보안은 서비스 레이어에서 강제한다 — 매퍼는 원천 데이터를 반환하되,
 * 응찰자 뷰로 나가는 순위/상세는 서비스가 타사 금액·업체명을 마스킹한다.
 */
@Mapper
public interface AuctionMapper {

    String AUCTION_COLS = """
            SELECT a.id,
                   a.event_id           AS "eventId",
                   a.title,
                   a.category,
                   a.auction_type       AS "auctionType",
                   a.award_criteria     AS "awardCriteria",
                   a.weight_price       AS "weightPrice",
                   a.weight_reputation  AS "weightReputation",
                   a.weight_delivery    AS "weightDelivery",
                   a.round,
                   to_char(a.deadline AT TIME ZONE 'UTC','YYYY-MM-DD"T"HH24:MI:SS"Z"') AS "deadline",
                   CEIL(EXTRACT(EPOCH FROM (a.deadline - now())) / 86400.0)::int        AS "dday",
                   (now() > a.deadline)                                                 AS "closed",
                   a.materials,
                   a.accent,
                   a.material_package_id AS "materialPackageId",
                   (SELECT count(*) FROM bid b WHERE b.auction_id = a.id)::int          AS "bidderCount",
                   (SELECT min(b.total) FROM bid b WHERE b.auction_id = a.id)           AS "lowestPrice",
                   aw.bid_id            AS "awardBidId",
                   wc.name             AS "awardedCompany",
                   wb.total            AS "finalPrice"
            FROM auction a
            LEFT JOIN award   aw ON aw.auction_id = a.id
            LEFT JOIN bid     wb ON wb.id = aw.bid_id
            LEFT JOIN company wc ON wc.id = wb.company_id
            """;

    /** 옥션 목록 — eventId 선택 필터. 마감 임박(deadline) 우선 정렬. */
    @Select("<script>" + AUCTION_COLS
            + "<where>"
            + "  <if test='eventId != null and eventId != \"\"'>AND a.event_id = #{eventId}</if>"
            + "</where>"
            + " ORDER BY a.deadline DESC, a.created_at DESC LIMIT #{limit} OFFSET #{offset}"
            + "</script>")
    List<Map<String, Object>> listAuctions(@Param("eventId") String eventId,
                                           @Param("limit") int limit,
                                           @Param("offset") int offset);

    /** 옥션 단건. */
    @Select("<script>" + AUCTION_COLS + " WHERE a.id = #{id}</script>")
    Map<String, Object> findAuction(@Param("id") String id);

    /** 순위용 원천 응찰 목록(총액 오름차순). 서비스가 봉인 마스킹한다. */
    @Select("""
            SELECT b.company_id AS "companyId",
                   b.total,
                   b.lead_days  AS "leadDays"
            FROM bid b
            WHERE b.auction_id = #{auctionId}
            ORDER BY b.total ASC, b.submitted_at ASC
            """)
    List<Map<String, Object>> listRanking(@Param("auctionId") String auctionId);

    /** 발주자 뷰(마감 후) — 전 견적 + 업체명 + 평판 공개. */
    @Select("""
            SELECT b.id          AS "bidId",
                   b.company_id  AS "companyId",
                   c.name        AS "companyName",
                   b.total,
                   b.lead_days   AS "leadDays",
                   COALESCE(r.rating, 4.0)     AS "rating",
                   COALESCE(r.jobs_done, 0)    AS "jobsDone",
                   COALESCE(r.claim_rate, 0)   AS "claimRate"
            FROM bid b
            JOIN company c ON c.id = b.company_id
            LEFT JOIN company_reputation r ON r.company_id = b.company_id
            WHERE b.auction_id = #{auctionId}
            ORDER BY b.total ASC
            """)
    List<Map<String, Object>> listQuotes(@Param("auctionId") String auctionId);

    /** 사용자의 CONTRACTOR 소속 등록업체(행사 스코프). 봉인/응찰 게이트용. */
    @Select("""
            SELECT em.company_id  AS "companyId",
                   c.name         AS "companyName",
                   c.registered   AS "registered"
            FROM event_member em
            JOIN company c ON c.id = em.company_id
            WHERE em.user_id = #{userId}
              AND em.event_id = #{eventId}
              AND em.role_code = 'CONTRACTOR'
              AND em.company_id IS NOT NULL
            LIMIT 1
            """)
    Map<String, Object> findContractorCompany(@Param("userId") String userId,
                                              @Param("eventId") String eventId);

    /** 옥션 초대(등록업체) 총 수 — 0이면 공개 옥션으로 간주. */
    @Select("SELECT count(*) FROM auction_invite WHERE auction_id = #{auctionId}")
    int countInvites(@Param("auctionId") String auctionId);

    @Select("SELECT count(*) FROM auction_invite WHERE auction_id = #{auctionId} AND company_id = #{companyId}")
    int isInvited(@Param("auctionId") String auctionId, @Param("companyId") String companyId);

    @Select("""
            SELECT b.id, b.total, b.version, b.lead_days AS "leadDays"
            FROM bid b WHERE b.auction_id = #{auctionId} AND b.company_id = #{companyId}
            """)
    Map<String, Object> findBid(@Param("auctionId") String auctionId, @Param("companyId") String companyId);

    @Insert("""
            INSERT INTO bid (id, auction_id, company_id, bidder_user_id, round,
                             subtotal, vat, total, lead_days, valid_until, terms, lines)
            VALUES (#{id}, #{auctionId}, #{companyId}, #{bidderUserId}, #{round},
                    #{subtotal}, #{vat}, #{total}, #{leadDays},
                    CAST(#{validUntil} AS date), #{terms}, CAST(#{lines} AS jsonb))
            """)
    int insertBid(Map<String, Object> p);

    @Update("""
            UPDATE bid SET subtotal=#{subtotal}, vat=#{vat}, total=#{total}, lead_days=#{leadDays},
                   valid_until=CAST(#{validUntil} AS date), terms=#{terms}, lines=CAST(#{lines} AS jsonb),
                   round=#{round}, version = version + 1, bidder_user_id=#{bidderUserId}, updated_at=now()
            WHERE auction_id=#{auctionId} AND company_id=#{companyId}
            """)
    int updateBid(Map<String, Object> p);

    @Select("""
            SELECT aw.id, aw.bid_id AS "bidId", aw.reason,
                   c.name AS "companyName",
                   b.total AS "total",
                   to_char(aw.awarded_at AT TIME ZONE 'UTC','YYYY-MM-DD"T"HH24:MI:SS"Z"') AS "awardedAt"
            FROM award aw
            JOIN bid  b ON b.id = aw.bid_id
            JOIN company c ON c.id = b.company_id
            WHERE aw.auction_id = #{auctionId}
            """)
    Map<String, Object> findAward(@Param("auctionId") String auctionId);

    @Select("SELECT auction_id FROM bid WHERE id = #{bidId}")
    String findAuctionIdOfBid(@Param("bidId") String bidId);

    /** 라운드 마감 처리 — deadline 을 현재 시각으로 당겨 마감(closed) 상태로 전이. */
    @Update("UPDATE auction SET deadline = now() WHERE id = #{id} AND deadline > now()")
    int closeAuction(@Param("id") String id);

    @Insert("""
            INSERT INTO award (id, auction_id, bid_id, reason, awarded_by)
            VALUES (#{id}, #{auctionId}, #{bidId}, #{reason}, #{awardedBy})
            """)
    int insertAward(Map<String, Object> p);

    @Insert("""
            INSERT INTO auction (id, event_id, title, category, auction_type, award_criteria,
                                 weight_price, weight_reputation, weight_delivery, round, deadline,
                                 material_package_id, materials, accent, created_by)
            VALUES (#{id}, #{eventId}, #{title}, #{category}, #{auctionType}, #{awardCriteria},
                    #{weightPrice}, #{weightReputation}, #{weightDelivery}, #{round},
                    CAST(#{deadline} AS timestamptz), #{materialPackageId}, #{materials}, #{accent}, #{createdBy})
            """)
    int insertAuction(Map<String, Object> p);

    @Insert("""
            INSERT INTO auction_invite (id, auction_id, company_id)
            VALUES (#{id}, #{auctionId}, #{companyId})
            ON CONFLICT (auction_id, company_id) DO NOTHING
            """)
    int insertInvite(Map<String, Object> p);

    /** 초대 후보 검증 — 등록업체(registered=true)만 통과. 미등록 id는 반환되지 않는다. */
    @Select("<script>"
            + "SELECT id FROM company WHERE registered = true AND id IN "
            + "<foreach item='cid' collection='ids' open='(' separator=',' close=')'>#{cid}</foreach>"
            + "</script>")
    List<String> filterRegistered(@Param("ids") List<String> ids);

    // ── 업체 포털 대시보드(SCR-38) ──
    /** 사용자의 CONTRACTOR 소속 등록업체(행사 무관 — 대시보드용). */
    @Select("""
            SELECT em.company_id AS "companyId", c.name AS "companyName", c.registered AS "registered"
            FROM event_member em
            JOIN company c ON c.id = em.company_id
            WHERE em.user_id = #{userId} AND em.role_code = 'CONTRACTOR' AND em.company_id IS NOT NULL
            ORDER BY em.created_at DESC
            LIMIT 1
            """)
    Map<String, Object> findUserCompany(@Param("userId") String userId);

    /** 내 업체의 응찰 목록(진행중 옥션) — 자사 금액만. */
    @Select("""
            SELECT a.id AS "auctionId", a.title AS "auctionTitle",
                   b.total AS "myPrice",
                   CEIL(EXTRACT(EPOCH FROM (a.deadline - now())) / 86400.0)::int AS "dday",
                   (a.deadline < now()) AS "closed"
            FROM bid b
            JOIN auction a ON a.id = b.auction_id
            WHERE b.company_id = #{companyId}
            ORDER BY a.deadline ASC
            """)
    List<Map<String, Object>> listMyBids(@Param("companyId") String companyId);

    /** 특정 옥션에서 내 총액보다 낮은 응찰 수(내 순위 = cnt + 1). 봉인 안전(카운트만). */
    @Select("""
            SELECT count(*)::int FROM bid b
            WHERE b.auction_id = #{auctionId} AND b.total < #{myTotal}
            """)
    int lowerBidCount(@Param("auctionId") String auctionId, @Param("myTotal") long myTotal);

    /** 내 업체가 낙찰받은 부스(수주). */
    @Select("""
            SELECT a.id AS "auctionId", a.title AS "auctionTitle", a.category, e.name AS "eventName",
                   CEIL(EXTRACT(EPOCH FROM (a.deadline - now())) / 86400.0)::int AS "dday"
            FROM award aw
            JOIN bid b ON b.id = aw.bid_id
            JOIN auction a ON a.id = aw.auction_id
            JOIN event e ON e.id = a.event_id
            WHERE b.company_id = #{companyId}
            ORDER BY aw.awarded_at DESC
            """)
    List<Map<String, Object>> listMyAwardedBooths(@Param("companyId") String companyId);

    // ── 옥션 개설 통지 대상(§5B 인앱 + 이메일) ──────────────────────────────────
    /** 행사명(통지 본문용). */
    @Select("SELECT name FROM event WHERE id = #{eventId}")
    String findEventName(@Param("eventId") String eventId);

    /**
     * 초대 옥션의 초대 업체 소속 CONTRACTOR 사용자(통지 대상). email 은 발송 경로 한정 원문.
     * ★ 별칭 쌍따옴표 필수(카멜케이스 키 보존).
     */
    @Select("""
            SELECT DISTINCT u.id AS "userId", u.email AS "email", u.display_name AS "displayName"
            FROM auction_invite ai
            JOIN event_member em ON em.company_id = ai.company_id
             AND em.event_id = (SELECT event_id FROM auction WHERE id = #{auctionId})
            JOIN app_user u ON u.id = em.user_id
            WHERE ai.auction_id = #{auctionId}
              AND em.role_code = 'CONTRACTOR'
              AND u.status = 'ACTIVE'
            """)
    List<Map<String, Object>> listInvitedContractorUsers(@Param("auctionId") String auctionId);

    /** 공개 옥션(초대 0) — 행사 전체 CONTRACTOR 사용자(통지 대상). */
    @Select("""
            SELECT DISTINCT u.id AS "userId", u.email AS "email", u.display_name AS "displayName"
            FROM event_member em
            JOIN app_user u ON u.id = em.user_id
            WHERE em.event_id = #{eventId}
              AND em.role_code = 'CONTRACTOR'
              AND u.status = 'ACTIVE'
            """)
    List<Map<String, Object>> listEventContractorUsers(@Param("eventId") String eventId);

    // ── F035 BOQ 물량 집계(M2 booth/layout·M4 utility_order — 읽기 전용) ──────────
    /**
     * 부스 폴리곤 면적·둘레·부스수·최대높이 집계. boothNo 가 null 이면 행사 전체 부스, 있으면 해당 부스코드만.
     * SRID 0 = 홀 로컬 평면(미터) → ST_Area/ST_Perimeter 단위 = ㎡/m(§V3).
     */
    @Select("""
            SELECT COALESCE(sum(ST_Area(b.geom)), 0)      AS "areaM2",
                   COALESCE(sum(ST_Perimeter(b.geom)), 0) AS "perimeterM",
                   count(b.id)                            AS "boothCount",
                   COALESCE(max(b.height_m), 0)           AS "maxHeightM"
            FROM booth b
            JOIN layout l ON l.id = b.layout_id
            WHERE l.event_id = #{eventId}
              AND (#{boothNo}::text IS NULL OR b.booth_no = #{boothNo})
            """)
    Map<String, Object> boqBoothAggregate(@Param("eventId") String eventId,
                                          @Param("boothNo") String boothNo);

    /** 유틸리티 신청 건수(M4). boothNo 필터 동일. */
    @Select("""
            SELECT count(*)::int AS "orderCount"
            FROM utility_order uo
            JOIN booth b  ON b.id = uo.booth_id
            JOIN layout l ON l.id = b.layout_id
            WHERE l.event_id = #{eventId}
              AND (#{boothNo}::text IS NULL OR b.booth_no = #{boothNo})
            """)
    Map<String, Object> boqUtilityAggregate(@Param("eventId") String eventId,
                                            @Param("boothNo") String boothNo);
}
