"""
********************************************************************************
* Copyright (c) 2026 the Qrisp authors
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is
* available at https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************
"""

from uuid import UUID

from qrisp.interface import BatchedBackend


def IQMBackend(
    api_token,
    device_instance=None,
    server_url=None,
    compilation_options=None,
    transpiler=None,
    calibration_set_id: str | UUID | None = None,
    use_metrics: bool = False,
    use_timeslot: bool = False,
):
    """
    This function creates a :ref:`BatchedBackend` for executing circuits on IQM hardware.

    Parameters
    ----------
    api_token : str
        An API token retrieved from the IQM Resonance website or IQM backend.
    device_instance : str
        The device instance of the IQM backend such as ``garnet``.
        For an up-to-date list, see the IQM Resonance website.
        Required if server_url is not provided.
    server_url : str, optional
        The server URL of the IQM backend. If not provided, it defaults to IQM resonance
        using the device_instance. If a server URL is provided, a device instance should not be provided.
    compilation_options: CircuitCompilationOptions
        An object to specify several options regarding `pulse-level compilation <https://docs.meetiqm.com/iqm-client/api/iqm.iqm_client.models.CircuitCompilationOptions.html>`_.
    transpiler : callable, optional
        A function receiving and returning a QuantumCircuit, mapping the given
        circuit to a hardware friendly circuit. By default the `transpile_to_iqm <https://iqm-finland.github.io/qiskit-on-iqm/api/iqm.qiskit_iqm.iqm_naive_move_pass.transpile_to_IQM.html>`_
        function will be used.
    calibration_set_id: ID of the calibration set the backend will use.
        ``None`` means the IQM Server will be queried for the current default
        calibration set.
    use_metrics: If True, the backend will query the server for calibration data and related
        quality metrics, and pass these to the transpilation target(s). The default value is set
        to False until quality metrics become available on the Resonance API.

    Examples
    --------

    We evaluate a :ref:`QuantumFloat` multiplication on the 20-qubit IQM Garnet.

    >>> from qrisp.interface import IQMBackend
    >>> qrisp_garnet = IQMBackend(api_token = "YOUR_IQM_RESONANCE_TOKEN", device_instance = "garnet")
    >>> from qrisp import QuantumFloat
    >>> a = QuantumFloat(2)
    >>> a[:] = 2
    >>> b = a*a
    >>> b.get_measurement(backend = qrisp_garnet, shots = 1000)
    {4: 0.548,
     5: 0.082,
     0: 0.063,
     6: 0.042,
     8: 0.031,
     2: 0.029,
     12: 0.014,
     10: 0.03,
     1: 0.027,
     7: 0.025,
     15: 0.023,
     9: 0.021,
     14: 0.021,
     13: 0.018,
     11: 0.014,
     3: 0.012}

    **Manual qubit selection and routing**

    In the next example we showcase how to prevent automatic selection of qubits.
    For this we overide the transpilation procedure. The default transpilation
    calls the ``transpile_to_IQM`` function, which performs routing,
    automatic selection of suitable qubits, basis-gate transformation and other
    optimizations.

    For our example we will just transform to the required basis gates and ensure
    manually that our circuit has the correct connectivity.

    ::

        from qrisp import QuantumCircuit

        # The custom transpiler should receive and return a QuantumCircuit
        def custom_transpiler(qc: QuantumCircuit) -> QuantumCircuit:
            return qc.transpile(basis_gates = ["cz", "r", "measure", "reset"])

        custom_transpiled_garnet = IQMBackend("YOUR_IQM_RESONANCE_TOKEN",
                                   device_instance = "garnet",
                                   transpiler = custom_transpiler)

        # Create a bell state
        qc = QuantumCircuit(2)
        qc.h(0)
        qc.cx(0, 1)
        qc.measure(0)

        # execute
        meas_res = qc.run(shots = 10000, backend = custom_transpiled_garnet)

    """
    if not isinstance(api_token, str):
        raise TypeError(
            "api_token must be a string. You can create an API token on the IQM Resonance website."
        )

    # Validate that either server_url or device_instance is provided, but not both
    if server_url is not None and device_instance is not None:
        raise ValueError(
            "Please provide either a server_url or a device_instance, but not both."
        )

    if server_url is None and device_instance is None:
        raise ValueError("Please provide either a server_url or a device_instance.")

    if device_instance is not None and not isinstance(device_instance, str):
        raise TypeError(
            "device_instance must be a string. You can retrieve a list of available devices on the IQM Resonance website."
        )

    if server_url is not None and not isinstance(server_url, str):
        raise TypeError("server_url must be a string.")

    try:
        from iqm.iqm_client import CircuitCompilationOptions
        from iqm.iqm_client.iqm_client import IQMClient
        from iqm.qiskit_iqm import transpile_to_IQM
        from iqm.qiskit_iqm.iqm_provider import IQMBackend
    except ImportError:
        raise ImportError(
            "Please install qiskit-iqm to use the IQMBackend. You can do this by running `pip install qrisp[iqm]`."
        )

    # Construct the server URL based on device_instance if server_url is not provided
    if server_url is None:
        server_url = "https://resonance.meetiqm.com/"

    client = IQMClient(
        iqm_server_url=server_url, token=api_token, quantum_computer=device_instance
    )
    backend = IQMBackend(
        client, calibration_set_id=calibration_set_id, use_metrics=use_metrics
    )

    if compilation_options is None:
        compilation_options = CircuitCompilationOptions()

    if transpiler is None:

        def transpiler(qc):
            from qrisp import QuantumCircuit

            qiskit_qc = qc.to_qiskit()
            transpiled_qiskit_qc = transpile_to_IQM(qiskit_qc, backend)
            return QuantumCircuit.from_qiskit(transpiled_qiskit_qc)

    def run_batch_iqm(batch):

        circuit_batch = []
        shot_batch = []
        for qc, shots in batch:
            if device_instance == "sirius":
                qiskit_qc = transpile_to_IQM(qc.to_qiskit(), backend)
            else:
                transpiled_qc = transpiler(qc)
                qiskit_qc = transpiled_qc.to_qiskit()
            circuit_batch.append(backend.serialize_circuit(qiskit_qc))
            if shots is None:
                shots = 1000

            shot_batch.append(shots)

        job = client.submit_circuits(
            circuit_batch,
            options=compilation_options,
            shots=max(shot_batch),
            use_timeslot=use_timeslot,
        )

        job.wait_for_completion()
        answer = job.result()

        counts_batch = []
        for i in range(len(batch)):
            counts = answer[i]

            counts_dic = {}

            shots = batch[i][1]
            if shots is None:
                shots = 1000

            for j in range(shots):

                key_str = ""

                for k in counts.keys():
                    key_str += str(counts[k][j][0])

                if key_str in counts_dic:
                    counts_dic[key_str] += 1
                else:
                    counts_dic[key_str] = 1

            counts_batch.append(counts_dic)

        return counts_batch

    return BatchedBackend(run_batch_iqm)
