#!/usr/bin/env python3
from __future__ import annotations

from pathlib import Path
from datetime import datetime
from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, PageBreak,
    KeepTogether, ListFlowable, ListItem
)

OUT = Path('/data/workspace/outsourcing-initiative/02-bank-documents/ISP Documentation/User Guide/User Guide EBA Register and BCE Report_English_Version.pdf')
TODAY = '28/06/2026'

NAVY = colors.HexColor('#003A70')
TEAL = colors.HexColor('#00A3A1')
LIGHT_BLUE = colors.HexColor('#E8F1FA')
MID_BLUE = colors.HexColor('#D9EAF7')
GREY = colors.HexColor('#F3F5F7')
DARK_GREY = colors.HexColor('#4B5563')

styles = getSampleStyleSheet()
styles.add(ParagraphStyle('BankTitle', parent=styles['Title'], fontName='Helvetica-Bold', fontSize=24, leading=30, textColor=NAVY, alignment=TA_CENTER, spaceAfter=12))
styles.add(ParagraphStyle('BankSubTitle', parent=styles['Heading2'], fontName='Helvetica-Bold', fontSize=15, leading=19, textColor=TEAL, alignment=TA_CENTER, spaceAfter=18))
styles.add(ParagraphStyle('BankH1', parent=styles['Heading1'], fontName='Helvetica-Bold', fontSize=16, leading=20, textColor=NAVY, spaceBefore=12, spaceAfter=8))
styles.add(ParagraphStyle('BankH2', parent=styles['Heading2'], fontName='Helvetica-Bold', fontSize=12.5, leading=16, textColor=NAVY, spaceBefore=9, spaceAfter=6))
styles.add(ParagraphStyle('BankH3', parent=styles['Heading3'], fontName='Helvetica-Bold', fontSize=10.5, leading=13, textColor=TEAL, spaceBefore=7, spaceAfter=4))
styles.add(ParagraphStyle('BodyBank', parent=styles['BodyText'], fontName='Helvetica', fontSize=9.2, leading=12.5, textColor=colors.black, spaceAfter=5))
styles.add(ParagraphStyle('SmallBank', parent=styles['BodyText'], fontName='Helvetica', fontSize=7.5, leading=9.5, textColor=DARK_GREY, spaceAfter=3))
styles.add(ParagraphStyle('Caption', parent=styles['BodyText'], fontName='Helvetica-Oblique', fontSize=8, leading=10, textColor=DARK_GREY, alignment=TA_CENTER, spaceBefore=3, spaceAfter=7))
styles.add(ParagraphStyle('TOC', parent=styles['BodyText'], fontName='Helvetica', fontSize=9, leading=11.5, leftIndent=4, spaceAfter=3))


def p(txt: str, style='BodyBank'):
    return Paragraph(txt, styles[style])


def bullets(items):
    return ListFlowable([ListItem(p(i), leftIndent=10) for i in items], bulletType='bullet', start='circle', leftIndent=14, bulletFontSize=6)


def nums(items):
    return ListFlowable([ListItem(p(i), leftIndent=12) for i in items], bulletType='1', leftIndent=16, bulletFontSize=8)


def tbl(data, widths=None, header=True):
    t = Table([[p(str(c), 'BodyBank') for c in row] for row in data], colWidths=widths, hAlign='LEFT')
    style = [
        ('GRID', (0,0), (-1,-1), 0.35, colors.HexColor('#B8C7D9')),
        ('VALIGN', (0,0), (-1,-1), 'TOP'),
        ('LEFTPADDING', (0,0), (-1,-1), 5), ('RIGHTPADDING', (0,0), (-1,-1), 5),
        ('TOPPADDING', (0,0), (-1,-1), 4), ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('ROWBACKGROUNDS', (0,1 if header else 0), (-1,-1), [colors.white, GREY]),
    ]
    if header:
        style += [('BACKGROUND', (0,0), (-1,0), NAVY), ('TEXTCOLOR', (0,0), (-1,0), colors.white), ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold')]
    t.setStyle(TableStyle(style))
    return t


def page_canvas(canvas, doc):
    canvas.saveState()
    w, h = A4
    canvas.setFillColor(NAVY)
    canvas.rect(0, h-14*mm, w, 14*mm, fill=1, stroke=0)
    canvas.setFillColor(colors.white)
    canvas.setFont('Helvetica-Bold', 10)
    canvas.drawString(15*mm, h-9*mm, 'INTESA SANPAOLO')
    canvas.setFont('Helvetica', 8)
    canvas.drawRightString(w-15*mm, h-9*mm, 'User Guide - EBA Register and ECB Report')
    canvas.setFillColor(TEAL)
    canvas.rect(0, h-15*mm, w, 1.2*mm, fill=1, stroke=0)
    canvas.setStrokeColor(colors.HexColor('#D0D7DE'))
    canvas.line(15*mm, 15*mm, w-15*mm, 15*mm)
    canvas.setFillColor(DARK_GREY)
    canvas.setFont('Helvetica', 7)
    canvas.drawString(15*mm, 10*mm, f'English version translated on {TODAY} from the Italian source document.')
    canvas.drawRightString(w-15*mm, 10*mm, f'Page {doc.page}')
    canvas.restoreState()


def build():
    OUT.parent.mkdir(parents=True, exist_ok=True)
    doc = SimpleDocTemplate(str(OUT), pagesize=A4, rightMargin=16*mm, leftMargin=16*mm, topMargin=22*mm, bottomMargin=18*mm)
    story = []

    story += [Spacer(1, 40*mm), p('INTESA SANPAOLO', 'BankSubTitle'), p('EBA Register and ECB Report', 'BankTitle'), p('User Guide', 'BankSubTitle'), p('English Version', 'BankSubTitle'), Spacer(1, 20*mm)]
    story += [tbl([['Edition', 'Document status'], ['07/2023', 'Source Italian edition'], [TODAY, 'Translated into English']], [35*mm, 110*mm]), Spacer(1, 10*mm)]
    story += [p('This document is a professional English translation of the Italian user guide “Registro EBA e report BCE - Guida Utente”. The translation preserves the operational meaning, procedural sequencing, and the corporate documentation style for use by authorised internal users.', 'BodyBank')]
    story += [PageBreak()]

    story += [p('Change History', 'BankH1')]
    story += [tbl([
        ['Edition', 'Change'],
        ['01/2021', 'First draft.'],
        ['08/2022', 'Added: link to the Jaggaer portal / Format Contract; reconciliation rows relating to the child assessment.'],
        ['12/2022', 'Added specialised audit profile.'],
        ['07/2023', 'Consistency checks and ECB report.'],
        [TODAY, 'Translated into English.'],
    ], [35*mm, 125*mm])]
    story += [Spacer(1, 8*mm), p('Contents', 'BankH1')]
    contents = [
        '1. Roles', '2. Access to the ServiceNow Back-End', '3. EBA Register', '3.1 Accessing an individual contract in the EBA Register', '3.2 Exporting contracts', '3.3 Filters', '3.4 Individual contract', '3.4.1 Consistency checks', '3.4.2 Audit fields', '3.5 Terminated contracts', '3.6 FEI sub-contractors', '4. Reconciliation Page', '4.1 Accessing a contract in the Reconciliation Page', '4.2 Reconciliation action', '4.3 Reconciliation logs', '5. ECB Report'
    ]
    for c in contents:
        story.append(p(c, 'TOC'))
    story += [PageBreak()]

    story += [p('1. Roles', 'BankH1')]
    story += [tbl([
        ['Role / Profile', 'Authorisations through the Outsourcing Portal back-end'],
        ['GO - Outsourcing Governance Structure', 'Read and write access to all EBA Register fields. Read access to all Reconciliation Page fields.'],
        ['Audit Profile', 'Read access to all EBA Register fields. Read access to all Reconciliation Page fields.'],
        ['Specialised Audit Profile', 'Read access to all EBA Register and Reconciliation Page fields. May update the audit-related fields: “Last audit assessment”, “Date of last audit assessment” and “Scheduled audit dates”.'],
    ], [45*mm, 115*mm])]

    story += [p('2. Access to the ServiceNow Back-End', 'BankH1')]
    story += [p('The GO user / Audit user accesses the ServiceNow back-end through the Outsourcing Portal as follows:', 'BodyBank'), nums([
        'Access the portal using the following link: https://isgs.service-now.com/isp_common_portal.',
        'Click the “Menu” icon.',
        'Select “Switch to management”.'
    ]), p('Figure 1 - Access to the ServiceNow back-end.', 'Caption')]

    story += [p('3. EBA Register', 'BankH1')]
    story += [p('3.1 Accessing an individual contract in the EBA Register', 'BankH2')]
    story += [p('From the ServiceNow back-end, the GO user / Audit user can access a contract in the EBA Register by performing the following steps:', 'BodyBank'), nums([
        'Enter “EBA Register” in the Filter Navigator.',
        'Select the “EBA Register” module in the “EBA Register” section.',
        'Select the relevant contract from the list.'
    ]), p('The user may choose the desired language by opening the system settings in the top right-hand corner, selecting the “General” tab, and choosing the relevant value in “Language”.', 'BodyBank')]

    story += [p('3.2 Exporting contracts', 'BankH2')]
    story += [p('The GO user / Audit user can save and export the EBA Register content in CSV format:', 'BodyBank'), nums(['Click the Context Column Menu.', 'Select “Export”.', 'Select “CSV”.'])]

    story += [p('3.3 Filters', 'BankH2')]
    story += [p('The user can perform advanced searches through filters. Multiple conditions can be entered and connected using logical operators.', 'BodyBank'), bullets([
        'The logical operator “AND” returns records that meet all requested conditions.',
        'The logical operator “OR” returns records that meet at least one of the requested conditions.'
    ]), nums([
        'Click the filter icon.',
        'Enter the conditions to be met. Use “AND” to add a condition that must be met; use “OR” where at least one condition must be met.',
        'Click “Run” to obtain the relevant record(s).'
    ]), p('The GO user may save a created filter by pressing “Save”, entering the filter name in “Save as”, and pressing “Save”. To retrieve a saved filter, use the Context List Menu, select “Filters”, then select the required filter.', 'BodyBank')]

    story += [p('3.4 Individual Contract', 'BankH2')]
    story += [p('The GO user can view and edit all fields contained in the EBA Register. The Audit user can only view them.', 'BodyBank')]
    story += [tbl([
        ['Prefix', 'Source / meaning'],
        ['EJ', 'Data originates from extra-group Jaggaer. For Jaggaer-source contracts only, the GO user can view assessment data even when the assessment process is not closed.'],
        ['ID', 'Data originates from intra-group DBEI.'],
        ['IO', 'Intra-group other contracts, originating from Format Contract.'],
        ['EO', 'Extra-group other contracts, originating from Format Contract.'],
    ], [25*mm, 135*mm])]
    story += [p('By clicking the Format Contract / Jaggaer link, the GO user is redirected to the Format Contract or Jaggaer portal respectively. In both cases, the user can return directly to the EBA Register.', 'BodyBank')]

    story += [p('3.4.1 Consistency Checks', 'BankH2')]
    story += [p('3.4.1.1 Decision-making structure/body approving the outsourcing', 'BankH3')]
    story += [p('The field “Decision-making structure/body approving the outsourcing” may be populated with one of the following values:', 'BodyBank'), bullets(['Board of Directors', 'Executive Committee', 'Chief Executive Officer / General Manager', 'Area Manager', 'Other'])]
    story += [p('Where “Other” is selected, the field “Decision-making structure/body approving the outsourcing (other)” is displayed and must be populated.', 'BodyBank')]

    story += [p('3.4.1.2 List of alternative suppliers', 'BankH3')]
    story += [tbl([
        ['Condition', 'System behaviour / required value'],
        ['“Is it possible to replace the supplier?” = No', '“Supplier replaceability assessment” is automatically set to “Impossible”. “List of alternative suppliers” is not editable.'],
        ['“Is it possible to replace the supplier?” = Yes', '“Supplier replaceability assessment” may only be “Easy” or “Difficult”. “Re-internalisation assessment of the outsourced function” may be “Easy”, “Difficult” or “Impossible”. “List of alternative suppliers” is editable and one or more extra-group and/or intra-group suppliers may be selected. “Alternative suppliers not in list” appears only if “Suppliers not in list” is flagged.'],
    ], [55*mm, 105*mm])]

    story += [p('3.4.1.3 Other Group companies as counterparties', 'BankH3')]
    story += [p('If “The contract includes other Group companies as counterparties” is set to “Yes”, at least the commissioning company must be selected in “Corporate perimeter”. If it is set to “No”, no check is applied to the “Corporate perimeter” field.', 'BodyBank')]

    story += [p('3.4.1.4 Contract dates and notice periods', 'BankH3')]
    story += [bullets([
        '“Contract start date”, “Next contract renewal date” and “Contract expiry date” must be entered in increasing chronological order.',
        'The contract expiry date must be exactly the day after the next contract renewal date.',
        'Exception: where “Contract expiry date” is set to 31/12/2050, “Next contract renewal date” is automatically set to 31/12/2050 without user intervention.',
        'On the contract expiry date, “Terminated contract” is automatically set to “Yes”.',
        'When “Terminated contract” is changed from “No” to “Yes” in the EBA Register, “Next contract renewal date” is automatically cleared.',
        'If notice period fields are not “N/A”, they must be shorter than the contract duration in days.'
    ])]

    story += [p('3.4.1.5 Transfer or processing of personal data by the supplier', 'BankH3')]
    story += [p('If “Transfer or processing of personal data by the service provider” is set to “No”, the following fields are not editable or are automatically set to “No”: description of outsourced data including personal data; personal data processing outsourcing; countries where data is stored; personal data transfer to FEI sub-contractor; personal data processing outsourcing to FEI sub-contractor.', 'BodyBank')]

    story += [p('3.4.2 Audit Fields', 'BankH2')]
    story += [p('At contract activation, the GO user sees “Last audit assessment” and “Date of last audit assessment” in read-only mode. If “Last audit assessment” is not populated, it defaults to “No”. Audit field changes may only be performed by the Specialised Audit Profile.', 'BodyBank')]
    story += [p('To modify audit fields, the Specialised Audit Profile must:', 'BodyBank'), nums(['Select the module “EBA change audit fields”.', 'Click “New”.', 'Populate the contract “ID” to be modified.', 'Enter the new values in the fields to be modified.', 'Click “Submit”; the populated fields are updated in the EBA Register.'])]

    story += [p('3.5 Terminated Contracts', 'BankH2')]
    story += [p('The GO user / Audit user can view contracts terminated no more than 10 years earlier by entering “EBA Register” in the Filter Navigator and selecting “Expired contracts” in the “EBA Register” section.', 'BodyBank')]

    story += [p('3.6 FEI Sub-Contractors', 'BankH2')]
    story += [p('3.6.1 Access to the FEI Sub-Contractors module', 'BankH3')]
    story += [nums(['Enter “EBA Register” in the Filter Navigator.', 'Select “FEI Sub-contractors” in the “EBA Register” section.'])]
    story += [p('The GO user can export the list of FEI sub-contractors in CSV format by using the Context Column Menu, selecting “Export”, and selecting “CSV”.', 'BodyBank')]
    story += [p('3.6.2 Access to FEI Sub-Contractor information', 'BankH3')]
    story += [p('To view FEI sub-contractors associated with an extra-group Jaggaer contract, access the relevant contract, select “Load related lists” at the bottom of the page, and select the “FEI Sub-contractors” tab.', 'BodyBank')]
    story += [p('3.6.2.1 Amending FEI Sub-Contractor information', 'BankH3'), nums(['Click the value of the relevant feeding-system contract code.', 'Amend the editable fields in the form; grey fields cannot be amended.', 'Click “Update”.'])]
    story += [p('3.6.2.2 Associating a new FEI Sub-Contractor with a contract', 'BankH3'), nums(['Click “New”.', 'Populate the fields for the new sub-contractor in the new form.', 'Click “Submit”.'])]
    story += [p('3.6.2.3 Removing an FEI Sub-Contractor from a contract', 'BankH3'), nums(['Tick the box next to the record to be deleted.', 'Click “Actions on selected rows”.', 'Choose “Delete” from the drop-down menu.'])]
    story += [p('3.6.3 FEI Sub-Outsourcing consistency check', 'BankH3')]
    story += [p('If FEI sub-contractors already exist and the field “FEI sub-outsourcing or substantial part of FEI” is not set consistently, the user is warned to set the field to “Yes” or remove the FEI sub-contractor.', 'BodyBank')]

    story += [p('4. Reconciliation Page', 'BankH1')]
    story += [p('4.1 Accessing a contract in the Reconciliation Page', 'BankH2')]
    story += [nums(['Enter “EBA Register” in the Filter Navigator.', 'Select the “Reconciliation Page” module in the Application Menu.', 'Select the relevant contract from the list.'])]
    story += [p('The Reconciliation Page shows all fields that have changed compared with the latest GO-validated contract version stored in the EBA Register.', 'BodyBank')]
    story += [p('4.2 Reconciliation Action', 'BankH2')]
    story += [p('4.2.1 Reconciliation from the Reconciliation Page', 'BankH3')]
    story += [nums(['Select one or more records to reconcile.', 'Click “Actions on selected rows”.', 'Choose one of the available values: New DBEI Value; New Extra-group Format Contract Value; New Intra-group Format Contract Value; New Assessment Value; New Jaggaer Value; Value Registered in EBA.', 'Confirm that you wish to proceed with reconciliation. Once confirmed, the relevant records are removed from the Reconciliation Page.'])]
    story += [p('Where a child assessment is closed, the GO user must update the assessment number on the feeding source (Jaggaer / Format Contract). When the source sends data to the EBA Register, a reconciliation row is generated for the assessment number.', 'BodyBank')]
    story += [p('4.2.2 Reconciliation by accessing an individual contract', 'BankH3')]
    story += [nums(['Enter “EBA Register” in the Filter Navigator.', 'Select “Contract in progress” in the “EBA Register” section.', 'Select the relevant contract from the list.', 'Select “Load related lists” at the bottom of the page and view the Reconciliation Page.'])]
    story += [p('If two changes are made to the same data item by the same feeding source, the Reconciliation Page displays the latest value in chronological order and does not create two reconciliation rows.', 'BodyBank')]
    story += [p('4.2.2.1 Record of a reconciliation action', 'BankH3')]
    story += [p('If the GO user chooses “Value Registered in EBA”, the source value is discarded. If the same source subsequently sends the same discarded value again, no new reconciliation row is created because the reconciliation choice has already been made.', 'BodyBank')]
    story += [p('4.2.2.2 Activating an individual contract', 'BankH3')]
    story += [p('After reconciling all records relating to the relevant contract, the GO user must complete the mandatory audit fields and click “Activate”.', 'BodyBank')]

    story += [p('4.2.3 Consistency Check Pop-Up', 'BankH2')]
    story += [p('During reconciliation, if a check is not met, the row may still be accepted; however, the GO user is warned via a pop-up about the inconsistency that would be created.', 'BodyBank')]
    story += [tbl([
        ['Fields', 'Control'],
        ['Decision-making structure/body approving the outsourcing; other decision-making structure/body', 'If “Other” is selected, the “Other” field must be populated. Otherwise, the “Other” field must be blank.'],
        ['Supplier replaceability; alternative supplier list; supplier replaceability assessment', 'If supplier replacement is possible, the alternative supplier list must be populated and the assessment must be Easy/Difficult. If replacement is not possible, the list must be blank and assessment must be Impossible.'],
        ['Other Group companies as counterparties; corporate perimeter', 'If other Group companies are counterparties, the corporate perimeter must include at least the commissioning company. Otherwise, no perimeter controls apply.'],
        ['Contract dates and notice periods', 'Dates must be in increasing chronological order; expiry date must follow renewal date by one day; notice periods must be shorter than contract duration.'],
        ['Personal data transfer/processing fields', 'If personal data transfer/processing is “No”, related data description, storage countries and FEI sub-contractor personal-data fields must be blank or “No”. If “Yes”, all listed fields must be populated.'],
        ['FEI sub-outsourcing / substantial part of FEI; FEI sub-contractor legal name', 'If FEI sub-outsourcing is “Yes”, the FEI sub-contractor list must be populated; otherwise it must be blank.'],
    ], [55*mm, 105*mm])]

    story += [p('4.3 Reconciliation Logs', 'BankH2')]
    story += [p('The GO user / Audit user can view the log table, including the “Operation” field, which indicates the choice made during reconciliation or whether the change originates from an external tool.', 'BodyBank')]
    story += [nums(['Enter “EBA Register” in the Filter Navigator.', 'Select “Reconciliation Log” in the “EBA Register” section.', 'View the “Operation” field in the log table.'])]

    story += [p('5. ECB Report', 'BankH1')]
    story += [p('Users enabled for ECB extraction can view the extraction automatically generated on the last day of each month and can generate an on-demand extraction.', 'BodyBank')]
    story += [p('Monthly automatic extraction', 'BankH2'), nums(['Enter “ECB” in the Filter Navigator.', 'Select the “ECB Report” module.', 'Access the required extraction, e.g. “ECB - 31/05/2023”. In this case, all positions are extracted regardless of supervisory authority.'])]
    story += [p('On-demand extraction', 'BankH2'), nums(['Select the “ECB Report” module.', 'Click “New”.', 'Select the supervisory authority to extract.', 'Click “Submit” to obtain the extraction.'])]
    story += [p('Converting the .txt extraction to Excel', 'BankH2')]
    story += [nums(['Download the required .txt file.', 'Open it in Excel.', 'In the Text Import Wizard, set File origin to “65001: Unicode (UTF-8)”.', 'Select “Delimited” and click “Next”.', 'Select “Other” and enter the pipe delimiter “|”. Click “Next”.', 'Select all columns.', 'Set the column data format to “Text”.', 'Click “Finish”.'])]

    story += [Spacer(1, 8*mm), p('Document control note', 'BankH1')]
    story += [p('This English version has been prepared for operational use. Screenshots and system labels in the original Italian source may remain in the underlying systems; users should follow the translated procedural steps while referring to the live ServiceNow screens for exact navigation labels where applicable.', 'SmallBank')]

    doc.build(story, onFirstPage=page_canvas, onLaterPages=page_canvas)
    print(OUT)
    print(OUT.stat().st_size)

if __name__ == '__main__':
    build()
