import os
import time

from flask import Blueprint, request, make_response, render_template, redirect, url_for, flash, session
from flask_paginate import Pagination, get_page_parameter
from flask_babel import gettext as _

from changedetectionio import forms
from changedetectionio import processors
from changedetectionio import worker_pool
from changedetectionio.store import ChangeDetectionStore
from changedetectionio.auth_decorator import login_optionally_required
# Shared filtering — the single source of truth, also used by the ui blueprint's
# bulk actions so a filtered view and the actions taken on it always agree.
from changedetectionio.blueprint.watchlist import filters as wl_filters
from changedetectionio.blueprint.watchlist.row_context import watch_row_context

def construct_blueprint(datastore: ChangeDetectionStore, update_q, queuedWatchMetaData):
    watchlist_blueprint = Blueprint('watchlist', __name__, template_folder="templates")

    @watchlist_blueprint.route("/", methods=['GET'])
    @login_optionally_required
    def index():
        active_tag_req = request.args.get('tag', '').lower().strip()
        active_tag_uuid = active_tag = None

        # Be sure limit_tag is a uuid
        if active_tag_req:
            for uuid, tag in datastore.data['settings']['application'].get('tags', {}).items():
                if active_tag_req == tag.get('title', '').lower().strip() or active_tag_req == uuid:
                    active_tag = tag
                    active_tag_uuid = uuid
                    break

        # Redirect for the old rss path which used the /?rss=true
        if request.args.get('rss'):
            return redirect(url_for('rss.feed', tag=active_tag_uuid))

        op = request.args.get('op')
        if op:
            uuid = request.args.get('uuid')
            if op == 'pause':
                datastore.data['watching'][uuid].toggle_pause()
            elif op == 'mute':
                datastore.data['watching'][uuid].toggle_mute()

            datastore.data['watching'][uuid].commit()
            return redirect(url_for('watchlist.index', tag = active_tag_uuid))

        # Sort by last_changed and add the uuid which is usually the key..
        sorted_watches = []
        active_processor = request.args.get('processor', '').strip()
        # Toolbar facet counts are tallied over the tag/processor/search context but
        # independently of the active status toggle, so the .seg numbers stay stable
        # as you switch between All / Unread / Deals / With errors.
        errored_count = 0
        deals_count = 0
        unread_count = 0
        processor_counts = {}
        list_filters = wl_filters.list_filters_from_args(datastore, request.args)
        for uuid, watch in datastore.data['watching'].items():
            # The processor facet is counted over the tag/search base (ignoring the
            # selected processor) so every detected processor shows up and you can
            # switch between them.
            if not wl_filters.watch_matches_tag(datastore, watch, list_filters):
                continue
            if not wl_filters.watch_passes_search(watch, list_filters):
                continue
            proc = watch.get('processor')
            if proc:
                processor_counts[proc] = processor_counts.get(proc, 0) + 1

            # Narrow to the selected processor for the remaining facets + the list.
            if list_filters['processor'] and proc != list_filters['processor']:
                continue
            if watch.get('last_error'):
                errored_count += 1
            if wl_filters.watch_is_deal(watch):
                deals_count += 1
            # Unread = changed and not yet viewed — same test the 'unread' status
            # filter uses, so this count matches what clicking Unread shows.
            if not (watch.viewed or watch.last_changed == 0):
                unread_count += 1
            if wl_filters.watch_passes_status(watch, list_filters):
                sorted_watches.append(watch)

        form = forms.quickWatchForm(request.form)
        page = request.args.get(get_page_parameter(), type=int, default=1)
        total_count = len(sorted_watches)

        pagination = Pagination(page=page,
                                total=total_count,
                                per_page=datastore.data['settings']['application'].get('pager_size', 50),
                                css_framework="semantic",
                                display_msg=_('displaying <b>{start} - {end}</b> {record_name} in total <b>{total}</b>'),
                                record_name=_('records'))

        sorted_tags = sorted(datastore.data['settings']['application'].get('tags').items(), key=lambda x: x[1]['title'])

        from changedetectionio import content_fetchers
        available_fetchers = content_fetchers.available_fetchers()

        from changedetectionio.llm.evaluator import get_llm_config as _get_llm_config
        from changedetectionio.llm.ui_strings import LLM_INTENT_WATCH_PLACEHOLDER
        llm_configured = bool(_get_llm_config(datastore))

        # Everything the row markup itself needs comes from here, shared with the Socket.IO
        # row push so a live-updated row can't drift from the server-rendered one.
        row_ctx = watch_row_context(datastore,
                                    active_tag_uuid=active_tag_uuid,
                                    queued_uuids=update_q.get_queued_uuids())

        output = render_template(
            "watch-overview.html",
            **row_ctx,
            active_tag=active_tag,
            active_processor=active_processor,
            checking_now_size=len(worker_pool.get_running_uuids()),
            app_rss_token=datastore.data['settings']['application'].get('rss_access_token'),
            errored_count=errored_count,
            deals_count=deals_count,
            unread_count=unread_count,
            processor_counts=processor_counts,
            # body classes for app-wide state; realtime.js keeps these in sync live
            # (has-any-unviewed reveals the "Mark all viewed" button - see _watch_table.scss)
            extra_classes=' '.join(filter(None, ['has-queue' if not update_q.empty() else '',
                                                 'llm-configured' if llm_configured else '',
                                                 'has-any-unviewed' if datastore.unread_changes_count else ''])),
            form=form,
            generate_tag_colors=processors.generate_processor_badge_colors,
            wcag_text_color=processors.wcag_text_color,
            guid=datastore.data['app_guid'],
            available_fetchers=available_fetchers,
            #header=_("todo - tag name etc"),
            hosted_sticky=os.getenv("SALTED_PASS", False) == False,
            now_time_server=round(time.time()),
            pagination=pagination,
            processor_badge_css=processors.get_processor_badge_css(),
            processor_badge_texts=processors.get_processor_badge_texts(),
            queue_size=update_q.qsize(),
            # Active view filters (tag/processor/q/unread/...) so links (e.g. column sorting)
            # can re-apply them and not drop the operator's current filtered view.
            active_filters=wl_filters.filter_query_args(request.args),
            search_q=request.args.get('q', '').strip(),
            sort_attribute=request.args.get('sort') if request.args.get('sort') else request.cookies.get('sort'),
            sort_order=request.args.get('order') if request.args.get('order') else request.cookies.get('order'),
            tags=sorted_tags,
            unread_changes_count=datastore.unread_changes_count,
            watches=sorted_watches,
            llm_configured=llm_configured,
            llm_intent_watch_placeholder=LLM_INTENT_WATCH_PLACEHOLDER,
        )

        # Return freed template-building memory to the OS immediately.
        # render_template allocates ~20MB of intermediate strings that are freed on return,
        # but glibc keeps those pages mapped in its arenas as RSS. malloc_trim() forces
        # glibc to release them, preventing RSS growth from concurrent Chrome connections.
        try:
            import ctypes
            ctypes.CDLL('libc.so.6').malloc_trim(0)
        except Exception:
            pass

        if session.get('share-link'):
            del (session['share-link'])

        resp = make_response(output)

        # The template can run on cookie or url query info
        if request.args.get('sort'):
            resp.set_cookie('sort', request.args.get('sort'))
        if request.args.get('order'):
            resp.set_cookie('order', request.args.get('order'))

        return resp

    @watchlist_blueprint.route("/uuids", methods=['GET'])
    @login_optionally_required
    def uuids():
        """All watch UUIDs matching the current filter (tag/search/status/processor).

        Backs the client "select all matching" feature: the watchlist page only
        renders one page of rows, so to select across pages the browser fetches the
        full matching id list from here and holds it in its selection store.
        """
        from flask import jsonify
        return jsonify({'uuids': wl_filters.matching_watch_uuids(datastore, request.args)})

    return watchlist_blueprint