import { prisma } from '@/libs/prisma'
import { SurveyBasicInfo, SurveyRegistRequest, SurveySearchParams } from '@/types/Survey'
import { convertToCamelCase, convertToSnakeCase } from '@/utils/common-utils'
import { Prisma } from '@prisma/client'
import type { SessionData } from '@/types/Auth'
import { HttpStatusCode } from 'axios'
import { ApiError } from 'next/dist/server/api-utils'
import { ERROR_MESSAGE } from '@/hooks/useAlertMsg'
import SurveySalePdf from '@/components/pdf/SurveySalePdf'
import React from 'react'
import { pdf, Document } from '@react-pdf/renderer'

type WhereCondition = {
  AND: any[]
  OR?: any[]
  [key: string]: any
}

/** 검색 옵션 */
const SEARCH_OPTIONS = [
  'BUILDING_NAME',
  'REPRESENTATIVE',
  'STORE',
  'STORE_ID',
  'CONSTRUCTION_POINT',
  'CONSTRUCTION_POINT_ID',
  'CUSTOMER_NAME',
  'POST_CODE',
  'ADDRESS',
  'ADDRESS_DETAIL',
  'SRL_NO',
] as const

/** 페이지당 기본 항목 수 */
const ITEMS_PER_PAGE = 10

/**
 * @description 조사 매물 서비스
 * @param {SurveySearchParams} params 검색 파라미터
 */
export class SurveySalesService {
  private params!: SurveySearchParams
  private session?: SessionData

  /**
   * @description 생성자
   * @param {SurveySearchParams} params 검색 파라미터
   */
  constructor(params: SurveySearchParams, session?: SessionData) {
    this.params = params
    this.session = session
  }

  /**
   * @description 권한 별 필수 값 존재 여부 확인, 없을 시 빈 데이터 반환
   * @param {SearchParams} params 검색 파라미터
   * @returns {NextResponse} 세션 체크 결과
   */
  checkSession(): ApiError | null {
    if (!this.session?.isLoggedIn || this.session?.role === null) {
      return new ApiError(HttpStatusCode.Unauthorized, ERROR_MESSAGE.UNAUTHORIZED)
    }
    if (this.session?.role === 'Builder' || this.session?.role === 'Partner') {
      if (this.params.builderId === null) {
        return new ApiError(HttpStatusCode.Forbidden, ERROR_MESSAGE.FORBIDDEN)
      }
    } else {
      if (this.session?.storeId === null) {
        return new ApiError(HttpStatusCode.Forbidden, ERROR_MESSAGE.FORBIDDEN)
      }
    }
    return null
  }

  /**
   * @description 내가 작성한 매물 조건 생성
   * @returns {WhereCondition} 내가 작성한 매물 조건
   */
  private createMySurveyCondition(): WhereCondition {
    if (!this.params.isMySurvey) return { AND: [] }
    return { AND: [{ REPRESENTATIVE_ID: this.params.isMySurvey }] }
  }

  /**
   * @description 키워드 검색 조건 생성
   * @returns {WhereCondition} 키워드 검색 조건
   */
  private createKeywordCondition(): WhereCondition {
    if (!this.params.keyword || !this.params.searchOption) return { AND: [] }

    const where: WhereCondition = { AND: [] }
    if (this.params.searchOption === 'all') {
      where.OR = SEARCH_OPTIONS.map((field) => ({
        [field]: { contains: this.params.keyword },
      }))
    } else if (SEARCH_OPTIONS.includes(this.params.searchOption?.toUpperCase() as (typeof SEARCH_OPTIONS)[number])) {
      where[this.params.searchOption?.toUpperCase() as (typeof SEARCH_OPTIONS)[number]] = { contains: this.params.keyword }
    }
    return where
  }

  /**
   * @description 권한 별 조회 조건 생성
   * @returns {WhereCondition} 역할 기반 조건
   * @exampleResult { AND: [{ STORE_ID: { equals: '1234567890' } }] }
   *
   * @description T01 : 임시저장되지 않은 전체 매물 조회 | T01 판매점에서 작성한 매물 조회
   * @description Admin : 같은 판매점에서 작성된 매물, 2차점에게 제출받은 매물 조회 | 본인이 작성한 매물 조회
   * @description Admin_Sub : 같은 판매점에서 작성된 매물, 시공권한 user에게 제출받은 매물 조회 | 본인이 작성한 매물 조회
   * @description Builder : 같은 시공점에서 작성된 매물 조회 | 본인이 작성한 매물 조회
   * @description Partner : 같은 시공점에서 작성된 매물 조회 | 본인이 작성한 매물 조회
   */
  private createRoleCondition(): WhereCondition {
    const where: WhereCondition = { AND: [] }

    switch (this.session?.role) {
      case 'Admin':
      case 'Admin_Sub':
        if (this.session?.storeId) {
          where.OR = [
            { AND: [{ STORE_ID: { equals: this.session.storeId } }] },
            { AND: [{ SUBMISSION_TARGET_ID: { equals: this.session.storeId } }, { SUBMISSION_STATUS: { equals: true } }] },
            { AND: [{ SUBMISSION_TARGET_NM: { equals: this.session.storeNm } }, { SUBMISSION_STATUS: { equals: true } }] },
          ]
        } else {
          where.AND.push({ REPRESENTATIVE_ID: { equals: this.session.userId } })
        }
        break
      case 'Builder':
      case 'Partner':
        if (this.session?.builderNo) {
          where.AND.push({ CONSTRUCTION_POINT_ID: { equals: this.session?.builderNo } })
        } else {
          where.AND.push({ REPRESENTATIVE_ID: { equals: this.session?.userId } })
        }
        break
      case 'T01':
        where.OR = [{ NOT: { SRL_NO: { startsWith: '一時保存' } } }, { STORE_ID: { equals: this.session?.storeId } }]
        break
      default:
        where.AND.push({ ID: { equals: -1 } })
        break
    }
    return where
  }

  /**
   * @description 조사 매물 검색 조건 생성
   * @returns {WhereCondition} 조사 매물 검색 조건
   */
  createFilterSurvey(): WhereCondition {
    const where: WhereCondition = { AND: [] }

    /** 내가 작성한 매물 조건 */
    const mySurveyCondition = this.createMySurveyCondition()
    if (mySurveyCondition.AND.length > 0) {
      where.AND.push(mySurveyCondition)
    }

    /** 키워드 검색 조건 */
    const keywordCondition = this.createKeywordCondition()
    if (Object.keys(keywordCondition).length > 0) {
      where.AND.push(keywordCondition)
    }

    /** 역할 기반 조건 */
    const roleCondition = this.createRoleCondition()
    if (Object.keys(roleCondition).length > 0) {
      where.AND.push(roleCondition)
    }
    /** 삭제된 매물 제외 */
    return { AND: [...where.AND, { DEL_YN: { equals: 'N' } }] }
  }

  /**
   * @description 조사 매물 검색
   * @param {WhereCondition} where 조사 매물 검색 조건
   * @returns {Promise<{ data: SurveyBasicInfo[], count: number }>} 조사 매물 데이터
   */
  async getSurveySales(): Promise<{ data: SurveyBasicInfo[]; count: number } | ApiError> {
    const sessionCheckResult = this.checkSession()
    if (sessionCheckResult) {
      return sessionCheckResult
    }

    const where = this.createFilterSurvey()
    /** 조사 매물 조회 */
    //@ts-ignore
    const surveys = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({
      where,
      orderBy: this.params.sort === 'created' ? { REG_DT: 'desc' } : { UPT_DT: 'desc' },
      skip: Number(this.params.offset),
      take: ITEMS_PER_PAGE,
    })

    /** 조사 매물 개수 조회 */
    //@ts-ignore
    const count = await prisma.SD_SURVEY_SALES_BASIC_INFO.count({ where })
    return { data: surveys as SurveyBasicInfo[], count }
  }

  /**
   * @description 조사 매물 생성
   * @param {SurveyRegistRequest} survey 조사 매물 데이터
   * @param {string} role 권한
   * @param {string} storeId 판매점 ID
   * @returns {Promise<SurveyBasicInfo>} 생성된 조사 매물 데이터
   */
  async createSurvey(survey: SurveyRegistRequest, role: string, storeId: string) {
    const { detailInfo, ...basicInfo } = survey
    const newSrlNo = survey.srlNo ?? (await this.getNewSrlNo(storeId, role))
    // @ts-ignore
    return await prisma.SD_SURVEY_SALES_BASIC_INFO.create({
      data: {
        ...convertToSnakeCase(basicInfo),
        SRL_NO: newSrlNo,
        DETAIL_INFO: {
          create: convertToSnakeCase(detailInfo),
        },
      },
    })
  }

  /**
   * @description 새로운 srlNo 생성 함수
   * @param {string} role 세션에 저장된 권한
   * @param {string} tempSrlNo 임시 srlNo (임시저장 시 사용)
   * @param {string} storeId 세션에 저장된 판매점 ID
   * @returns {Promise<string>} 새로운 srlNo
   *
   * @exampleResult HO250617001 (HO + 250617 + 001)
   */
  async getNewSrlNo(storeId: string, role: string) {
    const srlRole = role === 'T01' || role === 'Admin' ? 'HO' : role === 'Admin_Sub' || role === 'Builder' ? 'HM' : ''

    //@ts-ignore
    const index = await prisma.SD_SURVEY_SALES_BASIC_INFO.count({
      where: {
        SRL_NO: {
          startsWith: srlRole + storeId,
        },
      },
    })

    return (
      srlRole +
      storeId +
      new Date().getFullYear().toString().slice(-2) +
      (new Date().getMonth() + 1).toString().padStart(2, '0') +
      new Date().getDate().toString().padStart(2, '0') +
      (index + 1).toString().padStart(3, '0')
    )
  }

  /**
   * @description 조사 매물 상세 조회
   * @param {number} id 조사 매물 ID
   * @returns {Promise<SurveyBasicInfo>} 조사 매물 데이터
   */
  async fetchSurvey(id: number, isPdf: boolean): Promise<SurveyBasicInfo | ApiError | Blob> {
    // @ts-ignore
    const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({
      where: { ID: id, DEL_YN: 'N' },
      include: { DETAIL_INFO: true },
    })
    if (!result) {
      return new ApiError(HttpStatusCode.NotFound, ERROR_MESSAGE.NOT_FOUND)
    }
    if (!isPdf) {
      if (!this.session?.isLoggedIn) return new ApiError(HttpStatusCode.Unauthorized, ERROR_MESSAGE.UNAUTHORIZED)
      if (!this.checkRole(result, this.session as SessionData)) return new ApiError(HttpStatusCode.Forbidden, ERROR_MESSAGE.FORBIDDEN)
    }
    return result
  }

  /**
   * @description 조사 매물 PDF 생성
   * @param {SurveyBasicInfo} survey 조사 매물 데이터
   * @returns {Promise<Blob>} PDF Blob
   */
  async createSurveyPdf(survey: SurveyBasicInfo): Promise<Blob | ApiError> {
    if (!survey) {
      return new ApiError(HttpStatusCode.NotFound, ERROR_MESSAGE.NOT_FOUND)
    }
    try {
      const convertedSurvey = convertToCamelCase(survey) as SurveyBasicInfo
      const content = React.createElement(Document, null, React.createElement(SurveySalePdf, { survey: convertedSurvey }))
      const pdfBlob = await pdf(content).toBlob()
      const arrayBuffer = await pdfBlob.arrayBuffer()
      return new Blob([arrayBuffer], { type: 'application/pdf' })
    } catch (error) {
      return new ApiError(HttpStatusCode.InternalServerError, ERROR_MESSAGE.PDF_GENERATION_ERROR)
    }
  }

  /**
   * @description 조사 매물 수정
   * @param {number} id 조사 매물 ID
   * @param {SurveyRegistRequest} survey 조사 매물 데이터
   * @param {boolean} isTemporary 임시 저장 여부
   * @param {string} storeId 판매점 ID
   * @param {string} role 권한
   * @returns {Promise<SurveyBasicInfo>} 수정된 조사 매물 데이터
   */
  async updateSurvey(id: number, survey: SurveyRegistRequest, isTemporary: boolean, storeId: string, role: string) {
    const { detailInfo, ...basicInfo } = survey
    const newSrlNo = isTemporary ? '一時保存' : survey.srlNo === '一時保存' ? await this.getNewSrlNo(storeId, role) : survey.srlNo

    // @ts-ignore
    return (await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
      where: { ID: Number(id) },
      data: {
        ...convertToSnakeCase(basicInfo),
        SRL_NO: newSrlNo,
        UPT_DT: new Date(),
        DETAIL_INFO: {
          update: convertToSnakeCase(detailInfo),
        },
      },
      include: {
        DETAIL_INFO: true,
      },
    })) as SurveyBasicInfo
  }

  /**
   * @description 조사 매물 삭제
   * @param {number} id 조사 매물 ID
   */
  async deleteSurvey(id: number) {
    await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
      // @ts-ignore
      await tx.SD_SURVEY_SALES_BASIC_INFO.update({
        where: { ID: Number(id) },
        data: {
          DEL_YN: 'Y',
          UPT_DT: new Date(),
        },
      })
    })
  }

  /**
   * @description 조사 매물 제출
   * @param {number} id 조사 매물 ID
   * @param {string} targetId 제출 대상 ID
   * @param {string} targetNm 제출 대상 이름
   * @returns {Promise<SurveyBasicInfo>} 제출된 조사 매물 데이터
   */
  async submitSurvey(id: number, targetId: string, targetNm: string) {
    // @ts-ignore
    return (await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
      where: { ID: Number(id) },
      data: {
        SUBMISSION_STATUS: true,
        SUBMISSION_DATE: new Date(),
        SUBMISSION_TARGET_ID: targetId,
        SUBMISSION_TARGET_NM: targetNm,
        UPT_DT: new Date(),
      },
    })) as SurveyBasicInfo
  }

  /**
   * @description 권한 체크
   * @param {any} survey 조사 매물 데이터
   * @param {SessionData} session 세션 데이터
   * @returns {boolean} 해당 매물의 조회 권한 여부 (true: 권한 있음, false: 권한 없음)
   */
  checkRole(survey: any, session: SessionData): boolean {
    if (!survey || !session) return false

    const roleChecks = {
      T01: () => this.checkT01Role(survey, session.userId, session.storeId),
      Admin: () => this.checkAdminRole(survey, session.storeId, session.storeNm),
      Admin_Sub: () => this.checkAdminRole(survey, session.storeId, session.storeNm),
      Partner: () => this.checkPartnerOrBuilderRole(survey, session.builderNo, session.userId),
      Builder: () => this.checkPartnerOrBuilderRole(survey, session.builderNo, session.userId),
      default: () => false,
    }

    return roleChecks[session.role as keyof typeof roleChecks]?.() ?? false
  }

  /**
   * @description T01 권한 체크
   * - 본인이 작성한 임시저장 매물 혹은 T01 판매점에서 작성한 매물, 임시저장 매물을 제외한 전 매물 조회 가능
   *
   * @param {any} survey 조사 매물 데이터
   * @returns {boolean} 해당 매물의 조회 권한 여부 (true: 권한 있음, false: 권한 없음)
   */
  private checkT01Role(survey: any, userId: string | null, storeId: string | null): boolean {
    if (survey.REPRESENTATIVE_ID === userId || survey.STORE_ID === storeId) {
      return true
    }
    return survey.SRL_NO !== '一時保存'
  }

  /**
   * @description Admin, Admin_Sub 권한 체크 (1차점 - Order, 2차점 - Musubi)
   * - 같은 판매점에서 작성한 매물, 제출 받은 매물 조회 가능
   *
   * @param {any} survey 조사 매물 데이터
   * @param {string | null} storeId 판매점 ID
   * @returns {boolean} 해당 매물의 조회 권한 여부 (true: 권한 있음, false: 권한 없음)
   */
  private checkAdminRole(survey: any, storeId: string | null, storeNm: string | null): boolean {
    if (!storeId) return survey.REPRESENTATIVE_ID === this.session?.userId
    return survey.SUBMISSION_STATUS
      ? survey.SUBMISSION_TARGET_ID === storeId || survey.SUBMISSION_TARGET_NM === storeNm || survey.STORE_ID === storeId
      : survey.STORE_ID === storeId
  }

  /**
   * @description Partner, Builder 권한 체크
   * - 같은 시공점에서 작성한 매물 조회 가능
   * - 시공점ID가 없다면 본인이 작성한 매물 조회 가능
   *
   * @param {any} survey 조사 매물 데이터
   * @param {string | null} builderId 시공점 ID
   * @param {string | null} userId 유저 ID
   * @returns {boolean} 해당 매물의 조회 권한 여부 (true: 권한 있음, false: 권한 없음)
   */
  private checkPartnerOrBuilderRole(survey: any, builderId: string | null, userId: string | null): boolean {
    if (builderId) return survey.CONSTRUCTION_POINT_ID === builderId
    return survey.REPRESENTATIVE_ID === userId
  }

  /**
   * @description API ROUTE 에러 처리
   * @param {any} error 에러 객체
   * @returns {ApiError} 에러 객체
   */
  handleRouteError(error: any): ApiError {
    console.error('❌ API ROUTE ERROR : ', error)
    if (
      error instanceof Prisma.PrismaClientInitializationError ||
      error instanceof Prisma.PrismaClientUnknownRequestError ||
      error instanceof Prisma.PrismaClientRustPanicError ||
      error instanceof Prisma.PrismaClientValidationError ||
      error instanceof Prisma.PrismaClientKnownRequestError
    ) {
      return new ApiError(HttpStatusCode.InternalServerError, ERROR_MESSAGE.PRISMA_ERROR)
    }
    return new ApiError(error.statusCode ?? HttpStatusCode.InternalServerError, error.message ?? ERROR_MESSAGE.FETCH_ERROR)
  }

  /**
   * @description 비동기 함수 try-catch 처리 함수
   * @param {() => Promise<any>} func 비동기 함수
   * @returns {Promise<ApiError | any>} 에러 객체 또는 함수 결과
   */
  async tryFunction(func: () => Promise<any>): Promise<ApiError | any> {
    try {
      return await func()
    } catch (error) {
      return this.handleRouteError(error)
    }
  }
}
