import calendar
from datetime import date
from io import BytesIO

from openpyxl import Workbook
from openpyxl.chart import BarChart, Reference
from openpyxl.chart.series import SeriesLabel
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter

from .census import AREA_ORDER, CensusService

BLUE_FILL = PatternFill("solid", fgColor="FF2F75B5")
GREEN_FILL = PatternFill("solid", fgColor="FF00B050")
RED_FILL = PatternFill("solid", fgColor="FFFF0000")
LIGHT_FILL = PatternFill("solid", fgColor="FFD6E4F0")
HEADER_FONT = Font(name="Calibri", size=12, bold=True, color="FF000000")
HEADER_WHITE_FONT = Font(name="Calibri", size=12, bold=True, color="FFFFFFFF")
DATA_FONT = Font(name="Calibri", size=11)
TOTAL_FONT = Font(name="Calibri", size=12, bold=False, color="FFFFFFFF")
TITLE_FONT = Font(name="Calibri", size=72)
SUBTITLE_FONT = Font(name="Calibri", size=12, bold=True, color="FFFFFFFF")
COMP_TITLE_FONT = Font(name="Calibri", size=26, bold=True)
NUM_FMT = "#,##0"
DATE_FMT = "d-mmm"

THIN = Side(style="thin")
MEDIUM = Side(style="medium")
BORDER_ALL = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
BORDER_LEFT_MED = Border(left=MEDIUM, right=THIN, top=THIN, bottom=THIN)
BORDER_RIGHT_MED = Border(left=THIN, right=MEDIUM, top=THIN, bottom=THIN)
BORDER_BOTH_MED = Border(left=MEDIUM, right=MEDIUM, top=THIN, bottom=THIN)
BORDER_BOTTOM_MED = Border(left=THIN, right=THIN, top=THIN, bottom=MEDIUM)
CENTER = Alignment(horizontal="center", vertical="center")


def _write_year_block(ws, start_col, year, quarterly_data, year_totals):
    first_data_row = 7
    months_per_q = {1: [1, 2, 3], 2: [4, 5, 6], 3: [7, 8, 9], 4: [10, 11, 12]}

    title_cell = ws.cell(row=1, column=start_col, value=year)
    title_cell.font = TITLE_FONT
    title_cell.alignment = CENTER
    title_cell.border = Border(left=MEDIUM, right=MEDIUM, top=MEDIUM, bottom=MEDIUM)
    ws.merge_cells(
        start_row=1, start_column=start_col, end_row=4, end_column=start_col + 4
    )

    sub_row = 5
    sub_cell = ws.cell(
        row=sub_row, column=start_col, value=f"{year} - Auto Calculated"
    )
    sub_cell.font = SUBTITLE_FONT
    sub_cell.fill = PatternFill("solid", fgColor="FF44546A")
    sub_cell.alignment = CENTER
    sub_cell.border = Border(left=MEDIUM, right=THIN, top=MEDIUM, bottom=THIN)
    ws.merge_cells(
        start_row=sub_row, start_column=start_col, end_row=sub_row, end_column=start_col + 4
    )

    total_col = start_col + 5
    for r in [5, 6]:
        tc = ws.cell(row=r, column=total_col, value="Total" if r == 5 else None)
        tc.font = TOTAL_FONT
        tc.fill = RED_FILL
        tc.alignment = CENTER
        tc.border = Border(left=MEDIUM, right=MEDIUM, top=MEDIUM, bottom=THIN)
    ws.merge_cells(start_row=5, start_column=total_col, end_row=6, end_column=total_col)

    headers = ["Area/Unit"] + [f"Q{i}.{year}" for i in range(1, 5)]
    for i, h in enumerate(headers):
        c = ws.cell(row=6, column=start_col + i, value=h)
        c.font = HEADER_WHITE_FONT
        c.fill = BLUE_FILL
        c.alignment = CENTER
        c.border = BORDER_LEFT_MED if i == 0 else (BORDER_RIGHT_MED if i == 4 else BORDER_ALL)

    q_totals_by_area = {area: [0, 0, 0, 0] for area in AREA_ORDER}
    for qi in range(4):
        q_num = qi + 1
        q_data = quarterly_data.get(q_num, {"totals": {a: 0 for a in AREA_ORDER}})
        for area in AREA_ORDER:
            q_totals_by_area[area][qi] = q_data["totals"].get(area, 0)

    for ai, area in enumerate(AREA_ORDER):
        row = first_data_row + ai
        label = ws.cell(row=row, column=start_col, value=area)
        label.font = DATA_FONT
        label.alignment = CENTER
        label.border = BORDER_LEFT_MED
        for qi in range(4):
            val = q_totals_by_area[area][qi]
            c = ws.cell(row=row, column=start_col + 1 + qi, value=val)
            c.font = DATA_FONT
            c.number_format = NUM_FMT
            c.alignment = CENTER
            c.border = BORDER_RIGHT_MED if qi == 3 else BORDER_ALL

        col_letter = get_column_letter(start_col + 4)
        total_cell = ws.cell(row=row, column=total_col)
        parts = [f"{get_column_letter(start_col + 1 + qi)}{row}" for qi in range(4)]
        total_cell.value = "=" + "+".join(parts)
        total_cell.font = DATA_FONT
        total_cell.fill = LIGHT_FILL
        total_cell.number_format = NUM_FMT
        total_cell.alignment = CENTER
        total_cell.border = BORDER_BOTH_MED

    detail_start_row = 11
    for qi in range(4):
        q_num = qi + 1
        row_offset = detail_start_row + qi * 4
        q_months = months_per_q[q_num]
        q_data = quarterly_data.get(q_num, {"months": {}})

        ws.cell(row=row_offset, column=start_col, value="Area/Unit").font = HEADER_FONT
        ws.cell(row=row_offset, column=start_col).fill = BLUE_FILL
        ws.cell(row=row_offset, column=start_col).alignment = CENTER
        ws.cell(row=row_offset, column=start_col).border = BORDER_LEFT_MED

        for mi, m in enumerate(q_months):
            dt_str = f"{calendar.month_abbr[m]}"
            c = ws.cell(row=row_offset, column=start_col + 1 + mi, value=dt_str)
            c.font = HEADER_FONT
            c.fill = BLUE_FILL
            c.alignment = CENTER
            c.number_format = DATE_FMT
            c.border = BORDER_ALL

        tc = ws.cell(row=row_offset, column=start_col + 4, value="Total")
        tc.font = HEADER_FONT
        tc.fill = BLUE_FILL
        tc.alignment = CENTER
        tc.border = BORDER_RIGHT_MED

        for ai, area in enumerate(AREA_ORDER):
            dr = row_offset + 1 + ai
            lc = ws.cell(row=dr, column=start_col, value=area)
            lc.font = DATA_FONT
            lc.alignment = CENTER
            lc.border = BORDER_LEFT_MED

            for mi, m in enumerate(q_months):
                m_data = q_data.get("months", {}).get(m, {"OPD": 0, "ER": 0, "IP": 0})
                val = m_data.get(area, 0)
                c = ws.cell(row=dr, column=start_col + 1 + mi, value=val)
                c.font = DATA_FONT
                c.number_format = NUM_FMT
                c.alignment = CENTER
                c.border = BORDER_ALL

            col_letters = [get_column_letter(start_col + 1 + mi) for mi in range(3)]
            formula = f"={col_letters[0]}{dr}+{col_letters[1]}{dr}+{col_letters[2]}{dr}"
            t_cell = ws.cell(row=dr, column=start_col + 4, value=formula)
            t_cell.font = DATA_FONT
            t_cell.fill = LIGHT_FILL if year != date.today().year else GREEN_FILL
            t_cell.number_format = NUM_FMT
            t_cell.alignment = CENTER
            t_cell.border = BORDER_BOTH_MED

            if ai == 2:
                for ci in range(start_col, start_col + 6):
                    cell = ws.cell(row=dr, column=ci)
                    cell.border = Border(
                        left=MEDIUM if ci == start_col else THIN,
                        right=MEDIUM if ci in (start_col + 4, total_col) else THIN,
                        top=THIN,
                        bottom=MEDIUM,
                    )

    return total_col


def _write_comparison_table(ws, start_row, start_col, area_label, comparison_data, years, total_col_ref=None):
    label_font = Font(name="Calibri", size=12, bold=True)
    header_row = start_row
    headers = [area_label, "Q1", "Q2", "Q3", "Q4", "Total"]
    for i, h in enumerate(headers):
        c = ws.cell(row=header_row, column=start_col + i, value=h)
        c.font = HEADER_WHITE_FONT if i < 5 else Font(name="Calibri", size=12, bold=True, color="FFFFFFFF")
        c.fill = BLUE_FILL if i < 5 else GREEN_FILL
        c.alignment = CENTER
        c.border = BORDER_LEFT_MED if i == 0 else (BORDER_RIGHT_MED if i == 5 else BORDER_ALL)

    for yi, year in enumerate(years):
        r = header_row + 1 + yi
        ws.cell(row=r, column=start_col, value=year).font = label_font
        ws.cell(row=r, column=start_col).alignment = CENTER
        ws.cell(row=r, column=start_col).border = BORDER_LEFT_MED
        ws.cell(row=r, column=start_col).fill = PatternFill("solid", fgColor="FFFFFFFF")

        q_data = comparison_data.get(year, {}).get("quarterly", {})
        for qi in range(4):
            q_num = qi + 1
            val = q_data.get(q_num, {}).get("totals", {}).get(area_label, 0)
            c = ws.cell(row=r, column=start_col + 1 + qi, value=val)
            c.font = DATA_FONT
            c.number_format = NUM_FMT
            c.alignment = CENTER
            c.fill = LIGHT_FILL
            c.border = BORDER_RIGHT_MED if qi == 3 else BORDER_ALL

        cl = get_column_letter(start_col + 1)
        cl2 = get_column_letter(start_col + 4)
        formula = f"=SUM({cl}{r}:{cl2}{r})"
        tc = ws.cell(row=r, column=start_col + 5, value=formula)
        tc.font = DATA_FONT
        tc.number_format = NUM_FMT
        tc.alignment = CENTER
        tc.fill = LIGHT_FILL
        tc.border = BORDER_BOTH_MED

    return header_row + 1, header_row + len(years)


def _write_comparison_charts(ws, start_row, start_col, area_label, comparison_data, years, chart_width=8, chart_height=14):
    data_start_row = start_row + 1
    data_end_row = data_start_row + len(years) - 1

    chart = BarChart()
    chart.type = "col"
    chart.title = f"{area_label} Total Visits"
    chart.style = 10
    chart.width = chart_width
    chart.height = chart_height

    data_ref = Reference(ws, min_col=start_col + 1, min_row=data_start_row, max_col=start_col + 4, max_row=data_end_row)
    cats_ref = Reference(ws, min_col=start_col, min_row=data_start_row, max_row=data_end_row)

    for i, year in enumerate(years):
        series_data = Reference(
            ws,
            min_col=start_col + 1 + i,
            min_row=data_start_row,
            max_row=data_start_row,
        )

    chart.add_data(data_ref, from_rows=True, titles_from_data=False)
    chart.set_categories(cats_ref)

    for i, year in enumerate(years):
        chart.series[i].tx = SeriesLabel(v=str(year))

    chart_row = start_row + 2
    chart_col = start_col + 6
    ws.add_chart(chart, f"{get_column_letter(chart_col)}{chart_row}")

    return chart


def generate_census_excel(census_service, years=None):
    if years is None:
        years = sorted(
            set(
                [
                    census_service.year - 2,
                    census_service.year - 1,
                    census_service.year,
                ]
            )
        )

    comparison = census_service.get_comparison_data(years)
    wb = Workbook()
    ws = wb.active
    ws.title = "Census"

    year_blocks = {}
    for i, year in enumerate(years):
        col = 2 + i * 7
        q_data = comparison[year]["quarterly"]
        y_totals = comparison[year]["yearly"]
        _write_year_block(ws, col, year, q_data, y_totals)
        year_blocks[year] = col

    ws.merge_cells("A29:O29")
    title_cell = ws.cell(
        row=29,
        column=1,
        value=f"Comparison of Total Outpatient/Inpatient/ER Visits for {', '.join(str(y) for y in years)}",
    )
    title_cell.font = COMP_TITLE_FONT
    title_cell.alignment = CENTER
    title_cell.border = Border(left=MEDIUM, right=MEDIUM, top=MEDIUM, bottom=MEDIUM)
    ws.row_dimensions[29].height = 34.5

    area_labels = [("OPD", 31), ("IP", 49), ("ER", 68)]
    for area_label, table_row in area_labels:
        data_start, data_end = _write_comparison_table(
            ws, table_row, 1, area_label, comparison, years
        )
        _write_comparison_charts(ws, table_row, 1, area_label, comparison, years)

        summary_row = table_row + len(years) + 3
        label_cell = ws.cell(row=summary_row, column=5, value=area_label)
        label_cell.font = Font(name="Calibri", size=12, bold=True, color="FFFFFFFF")
        label_cell.fill = GREEN_FILL
        label_cell.alignment = CENTER
        label_cell.border = BORDER_BOTH_MED

        total_header = ws.cell(row=summary_row, column=6, value="Total")
        total_header.font = Font(name="Calibri", size=12, bold=True, color="FFFFFFFF")
        total_header.fill = GREEN_FILL
        total_header.alignment = CENTER
        total_header.border = BORDER_BOTH_MED

        for yi, year in enumerate(years):
            r = summary_row + 1 + yi
            yr_cell = ws.cell(row=r, column=5, value=year)
            yr_cell.font = DATA_FONT
            yr_cell.alignment = CENTER
            yr_cell.border = BORDER_LEFT_MED

            total_row = table_row + 1 + yi
            ref = f"F{total_row}"
            tv = ws.cell(row=r, column=6, value=f"={ref}")
            tv.font = DATA_FONT
            tv.number_format = NUM_FMT
            tv.alignment = CENTER
            tv.fill = LIGHT_FILL
            tv.border = BORDER_BOTH_MED

    ws.column_dimensions["A"].width = 17.29
    for col_idx in range(2, 50):
        letter = get_column_letter(col_idx)
        if letter in ("G", "N", "U"):
            ws.column_dimensions[letter].width = 9.29
        elif letter in ("H", "O"):
            ws.column_dimensions[letter].width = 14.0
        elif col_idx <= 22:
            ws.column_dimensions[letter].width = 12.29

    buf = BytesIO()
    wb.save(buf)
    buf.seek(0)
    return buf
