from fhir.resources.R4B.coverageeligibilityresponse import (
    CoverageEligibilityResponse as FHIRCoverageEligibilityResponse,
    CoverageEligibilityResponseInsuranceItem,
    CoverageEligibilityResponseInsurance,
    CoverageEligibilityResponseInsuranceItemBenefit,
)
from fhir.resources.R4B.money import Money
from fhir.resources.R4B.period import Period

from api_fhir_r4.configurations import (
    GeneralConfiguration,
    R4CoverageEligibilityConfiguration as Config,
)
from api_fhir_r4.converters import (
    BaseFHIRConverter,
    PatientConverter,
    ReferenceConverterMixin,
)
from api_fhir_r4.defaultConfig import DEFAULT_CFG
from api_fhir_r4.models import (
    CoverageEligibilityRequestV2 as FHIRCoverageEligibilityRequest,
)
from api_fhir_r4.utils import TimeUtils
from insuree.models import Insuree, InsureePolicy
from policy.models import Policy
from policy.services import EligibilityRequest, EligibilityService
from product.models import Product, ProductService, ProductItem
from uuid import UUID
from core.utils import get_current_user
from django.utils.translation import gettext as _


class CoverageEligibilityRequestConverter(BaseFHIRConverter):

    @classmethod
    def to_fhir_obj(
        cls,
        coverage_eligibility_response,
        coverage_eligibility_request,
        reference_type=ReferenceConverterMixin.UUID_REFERENCE_TYPE,
    ):
        fhir_response = cls.build_fhir_obligatory_fields(coverage_eligibility_request)
        insuree, fhir_response.patient = cls.build_fhir_patient(
            getattr(coverage_eligibility_request, 'chf_id', None),
            getattr(coverage_eligibility_request, 'patient_uuid', None)
        )

        if coverage_eligibility_request.policy_uuid:
            policies = Policy.objects.filter(
                uuid=coverage_eligibility_request.policy_uuid,
                insuree_policies__insuree=insuree,
                *Policy.filter_validity(prefix='insuree_policies__')
            )
        else:
            policies = Policy.objects.filter(
                product=coverage_eligibility_response.prod_id,
                insuree_policies__insuree=insuree,
                *Policy.filter_validity(prefix='insuree_policies__')
            )

        for item in policies:
            if item.status in Config.get_fhir_active_policy_status():
                cls.build_fhir_insurance(
                    fhir_response, item, coverage_eligibility_request
                )
        return fhir_response

    @classmethod
    def to_imis_obj(cls, fhir_coverage_eligibility_request, audit_user_id):
        fhir_coverage_eligibility_request = FHIRCoverageEligibilityRequest(
            **fhir_coverage_eligibility_request
        )
        chf_id = cls.build_imis_chf(fhir_coverage_eligibility_request)
        item_code, service_code = cls.build_imis_item_service(
            fhir_coverage_eligibility_request
        )
        policy_uuid = cls.build_imis_policy_uuid(fhir_coverage_eligibility_request)
        return EligibilityRequest(chf_id, service_code, item_code, policy_uuid)

    @classmethod
    def build_fhir_obligatory_fields(cls, coverage_eligibility_request):
        fhir_eligibility_response = {"status": "active", "outcome": "complete"}

        default_insurance_organisation = DEFAULT_CFG[
            "R4_fhir_insurance_organisation_config"
        ]
        resource_id = default_insurance_organisation["id"]
        reference_insurer = {"reference": f"Organization/{resource_id}"}
        fhir_eligibility_response["insurer"] = reference_insurer

        reference_patient = {
            "reference": f"Patient/{coverage_eligibility_request.chf_id}"
        }
        fhir_eligibility_response["patient"] = reference_patient

        reference_coverage_eligibility_request = {
            "reference": "CoverageEligibilityRequest"
        }
        fhir_eligibility_response["request"] = reference_coverage_eligibility_request

        fhir_eligibility_response["purpose"] = ["benefits"]
        fhir_eligibility_response["created"] = TimeUtils.date().isoformat()
        return FHIRCoverageEligibilityResponse(**fhir_eligibility_response)

    @classmethod
    def build_fhir_patient(cls, chf_id, patient_uuid):
        if chf_id:
            insuree = Insuree.objects.filter(chf_id=chf_id, *Insuree.filter_validity())
        elif patient_uuid:
            insuree = Insuree.objects.filter(uuid=patient_uuid, *Insuree.filter_validity())
        else:
            raise ValueError(_("policy.service.eligibility.insuree_id_or_uuid_missing"))
        if insuree.count() == 1:
            insuree = insuree.first()
            reference = PatientConverter.build_fhir_resource_reference(
                insuree,
                type='Patient',
                display=insuree.chf_id
            )
            return insuree, reference

    @classmethod
    def build_fhir_insurance(cls, fhir_response, item, request):
        result = CoverageEligibilityResponseInsurance.construct()
        cls.build_fhir_coverage(result, item.uuid)
        cls.build_fhir_benefit_period(result, item.start_date, item.expiry_date)
        # get the data from EligibilityService
        eligibility_request = EligibilityRequest(
            request.chf_id, request.service_code, request.item_code, item.uuid
        )
        response_eligibility_sp = EligibilityService(get_current_user()).request(
            eligibility_request
        )

        # build coverag item - product/benefit
        result.item = []
        cls.build_fhir_benefit_item_element(result, response_eligibility_sp)
        # check services and items etc
        prod_service = ProductService.objects.filter(
            product=response_eligibility_sp.prod_id, service__code=request.service_code, *ProductService.filter_validity()
        ).first()
        prod_item = ProductItem.objects.filter(
            product=response_eligibility_sp.prod_id, item__code=request.item_code, *ProductItem.filter_validity()
        ).first()
        # build coverage item - service
        if prod_service:
            cls.build_fhir_benefit_item_service_element(
                result, response_eligibility_sp, prod_service.service
            )
        # build coverage item - item
        if prod_item:
            cls.build_fhir_benefit_item_item_element(
                result, response_eligibility_sp, prod_item.item
            )
        if type(fhir_response.insurance) is not list:
            fhir_response.insurance = [result]
        else:
            fhir_response.insurance.append(result)

    @classmethod
    def build_fhir_coverage(cls, insurance, policy_uuid):
        # Due to circular dependency import has to be done inside of method
        from api_fhir_r4.converters import CoverageConverter

        policy = Policy.objects.filter(
            uuid=UUID(str(policy_uuid)), *Policy.filter_validity()
        ).first()
        reference_coverage = CoverageConverter.build_fhir_resource_reference(
            policy, type="Coverage", display=policy.uuid
        )
        insurance.coverage = reference_coverage

    @classmethod
    def build_fhir_benefit_period(cls, insurance, start_date, expiry_date):
        benefit_period = Period.construct()
        benefit_period.start = start_date
        benefit_period.end = expiry_date
        insurance.benefitPeriod = benefit_period

    @classmethod
    def build_fhir_benefit_item_element(cls, insurance, response):
        item = CoverageEligibilityResponseInsuranceItem.construct()
        system = f"{GeneralConfiguration.get_system_base_url()}CodeSystem/coverage-item-category"
        item.category = cls.build_codeable_concept(
            system=system, code="benefit", display="Benefit Package"
        )
        cls.__build_item_product_name(fhir_item=item, prod_id=response.prod_id)
        item.benefit = []
        if response.total_admissions_left:
            cls.build_fhir_int_item_benefit_element(
                item=item,
                code="admissions_left",
                display="total_admissions",
                value=response.total_admissions_left,
            )
        if response.total_consultations_left:
            cls.build_fhir_int_item_benefit_element(
                item=item,
                code="consultations_left",
                display="total_consultations",
                value=response.total_consultations_left,
            )
        if response.total_surgeries_left:
            cls.build_fhir_int_item_benefit_element(
                item=item,
                code="surgeries_left",
                display="total_surgeries",
                value=response.total_surgeries_left,
            )
        if response.total_deliveries_left:
            cls.build_fhir_int_item_benefit_element(
                item=item,
                code="deliveries_left",
                display="total_deliveries",
                value=response.total_deliveries_left,
            )
        if response.total_visits_left:
            cls.build_fhir_int_item_benefit_element(
                item=item,
                code="visits_left",
                display="total_visits",
                value=response.total_visits_left,
            )
        if response.total_antenatal_left:
            cls.build_fhir_int_item_benefit_element(
                item=item,
                code="antenatal_left",
                display="total_antenatal",
                value=response.total_antenatal_left,
            )
        if response.hospitalization_amount_left:
            cls.build_fhir_money_item_benefit_element(
                item=item,
                code="hospitalization_amount",
                display="hospitalization_amount",
                value=response.hospitalization_amount_left,
            )
        if response.delivery_amount_left:
            cls.build_fhir_money_item_benefit_element(
                item=item,
                code="delivery_amount",
                display="delivery_amount",
                value=response.delivery_amount_left,
            )
        if response.surgery_amount_left:
            cls.build_fhir_money_item_benefit_element(
                item=item,
                code="surgery_amount",
                display="surgery_amount",
                value=response.surgery_amount_left,
            )
        if response.consultation_amount_left:
            cls.build_fhir_money_item_benefit_element(
                item=item,
                code="consultation_amount",
                display="consultation_amount",
                value=response.consultation_amount_left,
            )
        if response.antenatal_amount_left:
            cls.build_fhir_money_item_benefit_element(
                item=item,
                code="antenatal_amount",
                display="antenatal_amount",
                value=response.antenatal_amount_left,
            )
        insurance.item.append(item)

    @classmethod
    def build_fhir_benefit_item_item_element(cls, insurance, response, stored_item):
        item = CoverageEligibilityResponseInsuranceItem.construct()
        system = f"{GeneralConfiguration.get_system_base_url()}CodeSystem/coverage-item-category"
        item.category = cls.build_codeable_concept(
            system=system, code="item", display="Item"
        )
        item.benefit = []
        if response.min_date_item:
            cls.build_fhir_string_item_benefit_element(
                item=item,
                code="min_date_item",
                display="Mininum date",
                value=response.min_date_item,
            )
        if response.item_left:
            cls.build_fhir_int_item_benefit_element(
                item=item,
                code="item_left",
                display="Items left",
                value=response.service_left,
            )
        code = stored_item.code
        item.productOrService = cls.build_simple_codeable_concept(code)
        item.excluded = not response.is_service_ok
        insurance.item.append(item)

    @classmethod
    def build_fhir_benefit_item_service_element(
        cls, insurance, response, stored_service
    ):
        item = CoverageEligibilityResponseInsuranceItem.construct()
        system = f"{GeneralConfiguration.get_system_base_url()}CodeSystem/coverage-item-category"
        item.category = cls.build_codeable_concept(
            system=system, code="service", display="Service"
        )
        item.benefit = []
        if response.min_date_service:
            cls.build_fhir_string_item_benefit_element(
                item=item,
                code="min_date_item",
                display="Mininum date",
                value=response.min_date_item,
            )
        if response.item_left:
            cls.build_fhir_int_item_benefit_element(
                item=item,
                code="service_left",
                display="Services left",
                value=response.item_left,
            )
        code = stored_service.code
        item.productOrService = cls.build_simple_codeable_concept(code)
        item.excluded = not response.is_item_ok
        insurance.item.append(item)

    @classmethod
    def build_fhir_int_item_benefit_element(cls, item, code, display, value):
        system = f"{GeneralConfiguration.get_system_base_url()}CodeSystem/coverage-item-benefit-type"
        benefit = CoverageEligibilityResponseInsuranceItemBenefit.construct()
        benefit.type = cls.build_codeable_concept(
            system=system, code=code, display=display
        )
        benefit.allowedUnsignedInt = value
        item.benefit.append(benefit)

    @classmethod
    def build_fhir_money_item_benefit_element(cls, item, code, display, value):
        system = f"{GeneralConfiguration.get_system_base_url()}CodeSystem/coverage-item-benefit-type"
        benefit = CoverageEligibilityResponseInsuranceItemBenefit.construct()
        benefit.type = cls.build_codeable_concept(
            system=system, code=code, display=display
        )
        money_value = Money.construct()
        money_value.value = value
        benefit.allowedMoney = money_value
        item.benefit.append(benefit)

    @classmethod
    def build_fhir_string_item_benefit_element(cls, item, code, display, value):
        system = f"{GeneralConfiguration.get_system_base_url()}CodeSystem/coverage-item-benefit-type"
        benefit = CoverageEligibilityResponseInsuranceItemBenefit.construct()
        benefit.type = cls.build_codeable_concept(
            system=system, code=code, display=display
        )
        benefit.allowedString = value
        item.benefit.append(benefit)

    @classmethod
    def build_imis_chf(cls, fhir_coverage_eligibility_request):
        chf_id = None
        patient_reference = fhir_coverage_eligibility_request.patient
        if patient_reference:
            chf_id = PatientConverter.get_resource_id_from_reference(patient_reference)
        return chf_id

    @classmethod
    def build_imis_item_service(cls, fhir_coverage_eligibility_request):
        service_code = None
        item_code = None
        if fhir_coverage_eligibility_request.item:
            for item in fhir_coverage_eligibility_request.item:
                type_service = cls.__get_code_from_codeable_concept_by_coding_code(
                    item.category
                )
                if type_service == "item":
                    item_code = item.productOrService.text
                if type_service == "service":
                    service_code = item.productOrService.text
        return item_code, service_code

    @classmethod
    def build_imis_policy_uuid(cls, fhir_coverage_eligibility_request):
        policy_uuid = None
        coverage_reference = getattr(fhir_coverage_eligibility_request, 'coverage', None)
        if coverage_reference:
            policy_uuid = cls.get_resource_id_from_reference(coverage_reference)
        return policy_uuid

    @classmethod
    def __get_code_from_codeable_concept_by_coding_code(cls, codeable_concept):
        service_code = None
        if codeable_concept:
            coding = cls.get_first_coding_from_codeable_concept(codeable_concept)
            if coding:
                service_code = coding.code
        return service_code

    @classmethod
    def __get_coverage_data(cls, response):
        policy = (
            InsureePolicy.objects.filter(
                insuree__chf_id=response.eligibility_request.chf_id, *InsureePolicy.filter_validity()
            )
            .first()
            .policy
        )
        product = Product.objects.get(id=response.prod_id, *Product.filter_validity())
        return policy, product

    @classmethod
    def __build_item_product_name(cls, fhir_item, prod_id):
        product_queryset = Product.objects.filter(id=prod_id, *Product.filter_validity())
        product = product_queryset.first()
        fhir_item.name = product.name
        fhir_item.description = product.code
