import { Injectable } from "@nestjs/common"
import { CreatePartyTypeDto } from "./dto/create-party-type.dto"
import { UpsertPartyPaymentDto } from "./dto/upsert-party-payment.dto"
import { PartyTypeRepository } from "./repositories/party-type.repository"
import { PartyPaymentRepository } from "./repositories/party-payment.repository"
import { ProjectRepository } from "../projects/repositories/project.repository"
import { ContractorRepository } from "../contractors/repositories/contractor.repository"
import { VendorRepository } from "../vendors/repositories/vendor.repository"
import { ConsultantRepository } from "../consultants/repositories/consultant.repository"
import {
  errorMessage,
  isEmpty,
  successMessage,
  validationMessage,
} from "../../utils/helpers"
import {
  failureResponse,
  successResponse,
} from "../../common/response/response"
import { code } from "../../common/response/response.code"
import { messageKey } from "../../constants/message-keys"
import { PartyType, PartyTypeCategory } from "./entities/party-type.entity"
import { PartyPayment } from "./entities/party-payment.entity"
import { PartyPaymentHistory } from "./entities/party-payment-history.entity"
import { Project } from "../projects/entities/project.entity"
import { verifyJwtToken } from "src/utils/jwt"
import { DataSource, Repository } from "typeorm"
import { InjectRepository } from "@nestjs/typeorm"

@Injectable()
export class PartyTypesService {
  constructor(
    private readonly partyTypeRepository: PartyTypeRepository,
    private readonly partyPaymentRepository: PartyPaymentRepository,
    private readonly contractorRepository: ContractorRepository,
    private readonly vendorRepository: VendorRepository,
    private readonly consultantRepository: ConsultantRepository,
    private readonly dataSource: DataSource,
    @InjectRepository(PartyPaymentHistory)
    private readonly partyPaymentHistoryRepository: Repository<PartyPaymentHistory>,
    @InjectRepository(Project)
    private readonly projectEntityRepository: Repository<Project>,
    private readonly projectRepository: ProjectRepository,
  ) {}

  async create(createPartyTypeDto: CreatePartyTypeDto, token: string) {
    const decoded = verifyJwtToken(token)
    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    // Check if party type with same name and category exists for the company
    const existingType = await this.partyTypeRepository.getByParams({
      where: {
        company_id: decoded.company_id,
        type_name: createPartyTypeDto.type_name,
        type_category: createPartyTypeDto.type_category,
      },
      findOne: true,
    })

    if (existingType) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.already_exist, {
          ":data": "Party Type",
          ":field": "type_name",
        }),
      )
    }

    let partyType: any = new PartyType()
    partyType = {
      ...createPartyTypeDto,
      company_id: decoded.company_id,
      created_by: decoded.user_id,
    }

    await this.partyTypeRepository.save(partyType)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_add, { ":data": "Party Type" }),
    )
  }

  async getTypesByCategory(category: PartyTypeCategory, token: string) {
    try {
      const decoded = verifyJwtToken(token)

      if (!decoded) {
        return failureResponse(
          code.VALIDATION,
          validationMessage(messageKey.invalid_token),
        )
      }

      const whereConditions: any = {
        type_category: category,
      }

      // Add filters
      // if (decoded.company_id) {
      //   whereConditions.company_id = decoded.company_id
      // }

      const partyTypes: any = await this.partyTypeRepository.getByParams({
        where: whereConditions,
        whereNull: ["deleted_at"],
        relations: ["company:id,name"],
        orderBy: { created_at: "DESC" },
      })

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_retrieve, { ":data": "Party Types" }),
        partyTypes,
      )
    } catch (error) {
      console.log(error, "error")

      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.exception),
      )
    }
  }

  async checkPartyTypeExist(
    typeName: string,
    category: PartyTypeCategory,
    companyId: number,
  ) {
    const partyType = await this.partyTypeRepository.getByParams({
      where: {
        type_name: typeName,
        type_category: category,
        company_id: companyId,
      },
      findOne: true,
    })

    return !isEmpty(partyType)
  }

  async upsertPayment(
    partyId: number,
    dto: UpsertPartyPaymentDto,
    token: string,
  ) {
    const decoded = verifyJwtToken(token)
    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    // Validate end_date is after start_date
    if (dto.end_date && new Date(dto.end_date) < new Date(dto.start_date)) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_date_range),
      )
    }

    const queryRunner = this.dataSource.createQueryRunner()
    await queryRunner.connect()
    await queryRunner.startTransaction()

    try {
      // Verify party exists based on party_type
      let partyExists = false
      switch (dto.party_type) {
        case PartyTypeCategory.CONTRACTOR:
          const contractor = await this.contractorRepository.getByParams({
            where: { id: partyId },
            findOne: true,
          })
          partyExists = !isEmpty(contractor)
          break
        case PartyTypeCategory.VENDOR:
          const vendor = await this.vendorRepository.getByParams({
            where: { id: partyId },
            findOne: true,
          })
          partyExists = !isEmpty(vendor)
          break
        case PartyTypeCategory.CONSULTANT:
          const consultant = await this.consultantRepository.getByParams({
            where: { id: partyId },
            findOne: true,
          })
          partyExists = !isEmpty(consultant)
          break
      }

      if (!partyExists) {
        await queryRunner.rollbackTransaction()
        return failureResponse(
          code.VALIDATION,
          validationMessage(messageKey.not_found, {
            ":data": dto.party_type.toLowerCase(),
          }),
        )
      }

      // Find existing payment
      const existingPayment = await this.partyPaymentRepository.getByParams({
        where: {
          party_id: partyId,
          party_type: dto.party_type,
        },
        whereNull: ["deleted_at"],
        findOne: true,
      })

      let savedPayment: PartyPayment

      if (existingPayment) {
        // Update existing payment
        const updatedPayment = {
          ...(existingPayment as any),
          payment_type: dto.payment_type,
          amount: dto.amount,
          start_date: dto.start_date ? new Date(dto.start_date) : null,
          end_date: dto.end_date ? new Date(dto.end_date) : null,
          paid_date: dto.paid_date ? new Date(dto.paid_date) : null,
          notes: dto.notes || null,
          company_id: dto.company_id,
          project_id: dto.project_id || null,
          updated_by: decoded.user_id,
        }

        savedPayment = await queryRunner.manager.save(
          PartyPayment,
          updatedPayment,
        )
      } else {
        // Create new payment
        const newPayment = new PartyPayment()
        newPayment.party_id = partyId
        newPayment.party_type = dto.party_type
        newPayment.payment_type = dto.payment_type
        newPayment.amount = dto.amount
        newPayment.start_date = dto.start_date ? new Date(dto.start_date) : null
        newPayment.end_date = dto.end_date ? new Date(dto.end_date) : null
        newPayment.paid_date = dto.paid_date ? new Date(dto.paid_date) : null
        newPayment.notes = dto.notes || null
        newPayment.company_id = dto.company_id
        newPayment.project_id = dto.project_id || null
        newPayment.created_by = decoded.user_id

        savedPayment = await queryRunner.manager.save(PartyPayment, newPayment)
      }

      // Process FIXED payment immediately within the same transaction
      if (savedPayment.payment_type === "FIXED") {
        const project = await queryRunner.manager.findOne(Project, {
          where: { id: savedPayment.project_id },
        })

        if (!project) {
          await queryRunner.rollbackTransaction()
          return failureResponse(
            code.VALIDATION,
            validationMessage(messageKey.not_found, {
              ":data": "Project",
            }),
          )
        }

        const currentBudget = Number(project.remaining_budget)
        const paymentAmount = Number(savedPayment.amount)

        if (currentBudget < paymentAmount) {
          await queryRunner.rollbackTransaction()
          return failureResponse(
            code.VALIDATION,
            `Insufficient budget. Required: ${paymentAmount}, Available: ${currentBudget}`,
          )
        }

        // Deduct budget
        project.remaining_budget = currentBudget - paymentAmount
        project.updated_at = new Date()

        await queryRunner.manager.save(Project, project)
      }

      await queryRunner.commitTransaction()

      return successResponse(
        code.SUCCESS,
        successMessage(
          existingPayment ? messageKey.data_update : messageKey.data_add,
          { ":data": "Party Payment" },
        ),
      )
    } catch (error) {
      console.log(error, "error")
      await queryRunner.rollbackTransaction()
      return failureResponse(
        code.VALIDATION,
        errorMessage(messageKey.exception),
      )
    } finally {
      await queryRunner.release()
    }
  }
}
