import psutil
import os
import threading
import gc
from sys import getswitchinterval

from opentelemetry import metrics
from opentelemetry.metrics import CallbackOptions, Observation
# from opentelemetry.sdk.resources import Resource
# from opentelemetry.sdk.metrics import MeterProvider
# from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
# from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter

mw_agent_target = os.environ.get('MW_AGENT_SERVICE', '127.0.0.1')


def collect_metrics() -> None:
    # metrics.set_meter_provider(MeterProvider(
    #     resource=Resource.create({
    #         "service.name": service_name,
    #         "project.name": project_name,
    #         "mw.app.lang": "python",
    #         "runtime.metrics.python": "true"
    #     }),
    #     metric_readers=[
    #         PeriodicExportingMetricReader(
    #             OTLPMetricExporter(insecure=True, endpoint=mw_agent_target + ":9319")
    #         )
    #     ],
    # ))
    provider = metrics.get_meter_provider()
    meter = provider.get_meter("middleware-apm", "0.2.0")
    _generate_metrics(meter)


def _generate_metrics(meter):
    meter.create_observable_gauge(
        "process.cpu_usage.percentage",
        unit="Percent",
        callbacks=[_cpu_usage_cb],
        description="The will show the cpu usage of the process"
    )
    meter.create_observable_gauge(
        "process.memory_usage.percentage",
        unit="Percent",
        callbacks=[_ram_usage_cb],
        description="The will show the memory usage of the process"
    )
    meter.create_observable_counter(
        "process.cpu.time",
        callbacks=[_cpu_time_callback],
        unit="s",
        description="This will show the cpu time of the process"
    )
    meter.create_observable_gauge(
        "process.memory.bytes",
        unit="Bytes",
        callbacks=[_memory_rss_cb, _memory_vms_cb, _memory_shared_cb, _memory_text_cb, _memory_data_cb],
        description="The will show the memory rss, vms, shared, text and data of the process"
    )
    meter.create_observable_gauge(
        "process.num_threads.count",
        unit="Count",
        callbacks=[_num_threads_cb],
        description="The will show the number of threads of the process"
    )
    meter.create_observable_gauge(
        "process.num_fds.count",
        unit="Count",
        callbacks=[_num_fds_cb],
        description="The will show the number of fds of the process"
    )
    meter.create_observable_gauge(
        "process.ctx_switches.count",
        unit="Count",
        callbacks=[_num_vctx_switches_cb, _num_ivctx_switches_cb],
        description="The will show the number of ctx switches of the process"
    )
    meter.create_observable_gauge(
        "process.num_connections.count",
        unit="Count",
        callbacks=[_num_connections_cb],
        description="The will show the number of connections of the process"
    )
    meter.create_observable_gauge(
        "process.io_counters.bytes",
        unit="Bytes",
        callbacks=[_io_read_bytes_cb, _io_write_bytes_cb],
        description="This will show the io read and write bytes of the process"
    )
    meter.create_observable_gauge(
        "process.cpu_affinity.count",
        unit="Count",
        callbacks=[_cpu_affinity_cb],
        description="The will show the cpu affinity of the process"
    )
    meter.create_observable_gauge(
        "process.create_time.timestamp",
        unit="s",
        callbacks=[_create_time_cb],
        description="The will show the create time of the process"
    )
    meter.create_observable_gauge(
        "process.open_files.count",
        unit="Count",
        callbacks=[_open_file_counts_cb],
        description="The will show the open files of the process"
    )
    meter.create_observable_gauge(
        "process.threads.count",
        unit="Count",
        callbacks=[_thread_counts_cb],
        description="The will show the threads of the process"
    )
    meter.create_observable_gauge(
        "process.gc.count",
        unit="Count",
        callbacks=[_gc0_cb, _gc1_cb, _gc2_cb],
        description="The will show the gc of the process"
    )
    meter.create_observable_gauge(
        "process.context_switches.count",
        unit="Count",
        callbacks=[_context_switch_cb],
        description="The will show the context switches of the process"
    )
    meter.create_observable_gauge(
        "process.disk_usage.percentage",
        unit="Percent",
        callbacks=[_disk_usage_percent_cb],
        description="The will show the disk usage percentage of the process"
    )
    meter.create_observable_gauge(
        "process.disk_usage.bytes",
        unit="Bytes",
        callbacks=[_disk_usage_total_cb, _disk_usage_used_cb, _disk_usage_free_cb],
        description="The will show the disk usage of the process"
    )


def _cpu_usage_cb(options: CallbackOptions):
    print("--- Metrics Generated ---")
    yield Observation(value=psutil.Process(os.getpid()).cpu_percent())


def _ram_usage_cb(options: CallbackOptions):
    yield Observation(value=psutil.Process(os.getpid()).memory_percent())


def _cpu_time_callback(options: CallbackOptions):
    cpu_times = psutil.Process(os.getpid()).cpu_times()
    yield Observation(value=cpu_times.user, attributes={"state": "user"})
    yield Observation(value=cpu_times.system, attributes={"state": "system"})
    yield Observation(value=cpu_times.children_user, attributes={"state": "children_user"})
    yield Observation(value=cpu_times.children_system, attributes={"state": "children_system"})
    yield Observation(value=cpu_times.iowait, attributes={"state": "iowait"})


def _memory_rss_cb(options: CallbackOptions):
    yield Observation(
        value=psutil.Process(os.getpid()).memory_info().rss,
        attributes={"type": "rss"}
    )


def _memory_vms_cb(options: CallbackOptions):
    yield Observation(
        value=psutil.Process(os.getpid()).memory_info().vms,
        attributes={"type": "vms"}
    )


def _memory_shared_cb(options: CallbackOptions):
    yield Observation(
        value=psutil.Process(os.getpid()).memory_info().shared,
        attributes={"type": "shared"}
    )


def _memory_text_cb(options: CallbackOptions):
    yield Observation(
        value=psutil.Process(os.getpid()).memory_info().text,
        attributes={"type": "text"}
    )


def _memory_data_cb(options: CallbackOptions):
    yield Observation(
        value=psutil.Process(os.getpid()).memory_info().data,
        attributes={"type": "data"}
    )


def _num_threads_cb(options: CallbackOptions):
    yield Observation(value=psutil.Process(os.getpid()).num_threads())


def _num_fds_cb(options: CallbackOptions):
    yield Observation(value=psutil.Process(os.getpid()).num_fds())


def _num_vctx_switches_cb(options: CallbackOptions):
    yield Observation(
        value=psutil.Process(os.getpid()).num_ctx_switches().voluntary,
        attributes={"type": "voluntary"}
    )


def _num_ivctx_switches_cb(options: CallbackOptions):
    yield Observation(
        value=psutil.Process(os.getpid()).num_ctx_switches().involuntary,
        attributes={"type": "involuntary"}
    )


def _num_connections_cb(options: CallbackOptions):
    yield Observation(value=len(psutil.Process(os.getpid()).connections()))


def _io_read_bytes_cb(options: CallbackOptions):
    yield Observation(
        value=psutil.Process(os.getpid()).io_counters().read_bytes,
        attributes={"direction": "read"}
    )


def _io_write_bytes_cb(options: CallbackOptions):
    yield Observation(
        value=psutil.Process(os.getpid()).io_counters().write_bytes,
        attributes={"direction": "write"}
    )


def _cpu_affinity_cb(options: CallbackOptions):
    yield Observation(value=len(psutil.Process(os.getpid()).cpu_affinity()))


def _create_time_cb(options: CallbackOptions):
    yield Observation(value=psutil.Process(os.getpid()).create_time())


def _open_file_counts_cb(options: CallbackOptions):
    yield Observation(value=len(psutil.Process(os.getpid()).open_files()))


def _thread_counts_cb(options: CallbackOptions):
    yield Observation(value=threading.active_count())


def _gc0_cb(options: CallbackOptions):
    yield Observation(value=gc.get_count()[0], attributes={"type": "gc0"})


def _gc1_cb(options: CallbackOptions):
    yield Observation(value=gc.get_count()[1], attributes={"type": "gc1"})


def _gc2_cb(options: CallbackOptions):
    yield Observation(value=gc.get_count()[2], attributes={"type": "gc2"})


def _context_switch_cb(options: CallbackOptions):
    yield Observation(value=getswitchinterval())


def _disk_usage_percent_cb(options: CallbackOptions):
    yield Observation(value=psutil.disk_usage(os.sep).percent)


def _disk_usage_total_cb(options: CallbackOptions):
    yield Observation(value=psutil.disk_usage(os.sep).total, attributes={"type": "total"})


def _disk_usage_used_cb(options: CallbackOptions):
    yield Observation(value=psutil.disk_usage(os.sep).used, attributes={"type": "used"})


def _disk_usage_free_cb(options: CallbackOptions):
    yield Observation(value=psutil.disk_usage(os.sep).free, attributes={"type": "free"})
